From a8386d01c21c1dd24ab1f86232b1d5d5633ad2eb Mon Sep 17 00:00:00 2001 From: "sergefp@mysql.com" <> Date: Mon, 5 Apr 2004 19:43:37 +0400 Subject: [PATCH 01/13] Many files: SQL Syntax for Prepared Statements (WL#1622) ps.test, ps.result: new file --- mysql-test/r/ps.result | 77 ++++++++++++++++ mysql-test/t/ps.test | 75 +++++++++++++++ sql/item.cc | 11 ++- sql/item.h | 1 + sql/lex.h | 2 + sql/mysql_priv.h | 5 +- sql/mysqld.cc | 3 + sql/sql_class.cc | 23 ++++- sql/sql_class.h | 14 +++ sql/sql_lex.h | 6 ++ sql/sql_parse.cc | 86 +++++++++++++++++- sql/sql_prepare.cc | 202 +++++++++++++++++++++++++++++++++++++---- sql/sql_yacc.yy | 74 +++++++++++++++ 13 files changed, 555 insertions(+), 24 deletions(-) create mode 100644 mysql-test/r/ps.result create mode 100644 mysql-test/t/ps.test diff --git a/mysql-test/r/ps.result b/mysql-test/r/ps.result new file mode 100644 index 00000000000..234c4af56f4 --- /dev/null +++ b/mysql-test/r/ps.result @@ -0,0 +1,77 @@ +drop table if exists t1,t2; +create table t1 +( +a int primary key, +b char(10), +); +insert into t1 values (1,'one'); +insert into t1 values (2,'two'); +insert into t1 values (3,'three'); +insert into t1 values (4,'four'); +set @a=2; +prepare stmt1 from 'select * from t1 where a <= ?'; +execute stmt1 using @a; +a b +1 one +2 two +set @a=3; +execute stmt1 using @a; +a b +1 one +2 two +3 three +deallocate prepare no_such_statement; +ERROR HY000: Undefined prepared statement +execute stmt1; +ERROR HY000: Wrong arguments to mysql_execute +prepare stmt2 from 'prepare nested_stmt from "select 1"'; +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 '"select 1"' at line 1 +prepare stmt2 from 'execute stmt1'; +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 'stmt1' at line 1 +prepare stmt2 from 'deallocate prepare z'; +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 'z' at line 1 +prepare stmt3 from 'insert into t1 values (?,?)'; +set @arg1=5, @arg2='five'; +execute stmt3 using @arg1, @arg2; +select * from t1 where a>3; +a b +4 four +5 five +prepare stmt4 from 'update t1 set a=? where b=?'; +set @arg1=55, @arg2='five'; +execute stmt4 using @arg1, @arg2; +select * from t1 where a>3; +a b +4 four +55 five +prepare stmt4 from 'create table t2 (a int)'; +execute stmt4; +prepare stmt4 from 'drop table t2'; +execute stmt4; +execute stmt4; +ERROR 42S02: Unknown table 't2' +prepare stmt5 from 'select ? + a from t1'; +set @a=1; +execute stmt5 using @a; +? + a +2 +3 +4 +5 +56 +execute stmt5 using @no_such_var; +? + a +NULL +NULL +NULL +NULL +NULL +set @nullvar=NULL; +execute stmt5 using @nullvar; +? + a +NULL +NULL +NULL +NULL +NULL +drop table t1; diff --git a/mysql-test/t/ps.test b/mysql-test/t/ps.test new file mode 100644 index 00000000000..a97de1a0de7 --- /dev/null +++ b/mysql-test/t/ps.test @@ -0,0 +1,75 @@ +# +# SQL Syntax for Prepared Statements test +# +--disable_warnings +drop table if exists t1,t2; +--enable_warnings + +create table t1 +( + a int primary key, + b char(10), +); +insert into t1 values (1,'one'); +insert into t1 values (2,'two'); +insert into t1 values (3,'three'); +insert into t1 values (4,'four'); + +# basic functionality +set @a=2; +prepare stmt1 from 'select * from t1 where a <= ?'; +execute stmt1 using @a; +set @a=3; +execute stmt1 using @a; + +# non-existant statement +--error 1243 +deallocate prepare no_such_statement; + +--error 1210 +execute stmt1; + +# Nesting ps commands is not allowed: +--error 1064 +prepare stmt2 from 'prepare nested_stmt from "select 1"'; + +--error 1064 +prepare stmt2 from 'execute stmt1'; + +--error 1064 +prepare stmt2 from 'deallocate prepare z'; + +# PS insert +prepare stmt3 from 'insert into t1 values (?,?)'; +set @arg1=5, @arg2='five'; +execute stmt3 using @arg1, @arg2; +select * from t1 where a>3; + +# PS update +prepare stmt4 from 'update t1 set a=? where b=?'; +set @arg1=55, @arg2='five'; +execute stmt4 using @arg1, @arg2; +select * from t1 where a>3; + +# PS create/delete +prepare stmt4 from 'create table t2 (a int)'; +execute stmt4; +prepare stmt4 from 'drop table t2'; +execute stmt4; + +# Do something that will cause error +--error 1051 +execute stmt4; + +# placeholders in result field names. +prepare stmt5 from 'select ? + a from t1'; +set @a=1; +execute stmt5 using @a; + +execute stmt5 using @no_such_var; + +set @nullvar=NULL; +execute stmt5 using @nullvar; + +drop table t1; + diff --git a/sql/item.cc b/sql/item.cc index 48e35f06ec3..eacee9b4653 100644 --- a/sql/item.cc +++ b/sql/item.cc @@ -610,16 +610,21 @@ void Item_param::set_double(double value) } -void Item_param::set_value(const char *str, uint length) +void Item_param::set_value(const char *str, uint length, CHARSET_INFO *ci) { DBUG_ENTER("Item_param::set_value"); - str_value.copy(str,length,default_charset()); + str_value.copy(str,length,ci); item_type= STRING_ITEM; value_is_set= 1; DBUG_PRINT("info", ("string: %s", str_value.ptr())); DBUG_VOID_RETURN; } +void Item_param::set_value(const char *str, uint length) +{ + set_value(str, length, default_charset()); +} + void Item_param::set_time(TIME *tm, timestamp_type type) { @@ -1471,7 +1476,7 @@ bool Item::send(Protocol *protocol, String *buffer) } case MYSQL_TYPE_TINY: { - longlong nr; + longlong nr; nr= val_int(); if (!null_value) result= protocol->store_tiny(nr); diff --git a/sql/item.h b/sql/item.h index dffa93eaac8..eea8bc011f4 100644 --- a/sql/item.h +++ b/sql/item.h @@ -385,6 +385,7 @@ public: void set_int(longlong i); void set_double(double i); void set_value(const char *str, uint length); + void set_value(const char *str, uint length, CHARSET_INFO *ci); void set_long_str(const char *str, ulong length); void set_long_binary(const char *str, ulong length); void set_longdata(const char *str, ulong length); diff --git a/sql/lex.h b/sql/lex.h index 3b32d2bcd3b..589579eda51 100644 --- a/sql/lex.h +++ b/sql/lex.h @@ -131,6 +131,7 @@ static SYMBOL symbols[] = { { "DAY_MICROSECOND", SYM(DAY_MICROSECOND_SYM)}, { "DAY_MINUTE", SYM(DAY_MINUTE_SYM)}, { "DAY_SECOND", SYM(DAY_SECOND_SYM)}, + { "DEALLOCATE", SYM(DEALLOCATE_SYM)}, { "DEC", SYM(DECIMAL_SYM)}, { "DECIMAL", SYM(DECIMAL_SYM)}, { "DEFAULT", SYM(DEFAULT)}, @@ -320,6 +321,7 @@ static SYMBOL symbols[] = { { "POINT", SYM(POINT_SYM)}, { "POLYGON", SYM(POLYGON)}, { "PRECISION", SYM(PRECISION)}, + { "PREPARE", SYM(PREPARE_SYM)}, { "PREV", SYM(PREV_SYM)}, { "PRIMARY", SYM(PRIMARY_SYM)}, { "PRIVILEGES", SYM(PRIVILEGES)}, diff --git a/sql/mysql_priv.h b/sql/mysql_priv.h index bd919d12348..7845d3199f4 100644 --- a/sql/mysql_priv.h +++ b/sql/mysql_priv.h @@ -604,8 +604,11 @@ int mysqld_show_column_types(THD *thd); int mysqld_help (THD *thd, const char *text); /* sql_prepare.cc */ -void mysql_stmt_prepare(THD *thd, char *packet, uint packet_length); +class Prepared_statement; +Prepared_statement *mysql_stmt_prepare(THD *thd, char *packet, + uint packet_length, bool text_protocol=false); void mysql_stmt_execute(THD *thd, char *packet, uint packet_length); +void mysql_sql_stmt_execute(THD *thd, Prepared_statement *stmt); void mysql_stmt_free(THD *thd, char *packet); void mysql_stmt_reset(THD *thd, char *packet); void mysql_stmt_get_longdata(THD *thd, char *pos, ulong packet_length); diff --git a/sql/mysqld.cc b/sql/mysqld.cc index d602c44c8f9..38e493ce5d6 100644 --- a/sql/mysqld.cc +++ b/sql/mysqld.cc @@ -4805,6 +4805,9 @@ struct show_var_st status_vars[]= { {"Com_unlock_tables", (char*) (com_stat+(uint) SQLCOM_UNLOCK_TABLES),SHOW_LONG}, {"Com_update", (char*) (com_stat+(uint) SQLCOM_UPDATE),SHOW_LONG}, {"Com_update_multi", (char*) (com_stat+(uint) SQLCOM_UPDATE_MULTI),SHOW_LONG}, + {"Com_prepare_sql", (char*) (com_stat+(uint) SQLCOM_PREPARE), SHOW_LONG}, + {"Com_execute_sql", (char*) (com_stat+(uint) SQLCOM_EXECUTE), SHOW_LONG}, + {"Com_dealloc_sql", (char*) (com_stat+(uint) SQLCOM_DEALLOCATE_PREPARE), SHOW_LONG}, {"Connections", (char*) &thread_id, SHOW_LONG_CONST}, {"Created_tmp_disk_tables", (char*) &created_tmp_disk_tables,SHOW_LONG}, {"Created_tmp_files", (char*) &my_tmp_file_created, SHOW_LONG}, diff --git a/sql/sql_class.cc b/sql/sql_class.cc index 1b4c8bec416..49fa0455a30 100644 --- a/sql/sql_class.cc +++ b/sql/sql_class.cc @@ -78,6 +78,23 @@ extern "C" void free_user_var(user_var_entry *entry) my_free((char*) entry,MYF(0)); } +/**************************************************************************** +** SQL syntax names for Prepared Statements +****************************************************************************/ + +extern "C" byte *get_stmt_key(SQL_PREP_STMT_ENTRY *entry, uint *length, + my_bool not_used __attribute__((unused))) +{ + *length=(uint) entry->name.length; + return (byte*) entry->name.str; +} + +extern "C" void free_sql_stmt(SQL_PREP_STMT_ENTRY *entry) +{ + char *pos= (char*) entry+ALIGN_SIZE(sizeof(*entry)); + my_free((char*) entry,MYF(0)); +} + /**************************************************************************** ** Thread specific functions @@ -160,7 +177,10 @@ THD::THD():user_time(0), current_statement(0), is_fatal_error(0), 16); else bzero((char*) &user_var_events, sizeof(user_var_events)); - + + hash_init(&sql_prepared_stmts, &my_charset_bin, USER_VARS_HASH_SIZE, 0, 0, + (hash_get_key) get_stmt_key, + (hash_free_key) free_sql_stmt,0); /* Protocol */ protocol= &protocol_simple; // Default protocol protocol_simple.init(this); @@ -279,6 +299,7 @@ void THD::cleanup(void) my_free((char*) variables.datetime_format, MYF(MY_ALLOW_ZERO_PTR)); delete_dynamic(&user_var_events); hash_free(&user_vars); + hash_free(&sql_prepared_stmts); if (global_read_lock) unlock_global_read_lock(this); if (ull) diff --git a/sql/sql_class.h b/sql/sql_class.h index 6815d0ae43c..22cb1197b21 100644 --- a/sql/sql_class.h +++ b/sql/sql_class.h @@ -594,6 +594,12 @@ public: struct system_variables variables; // Changeable local variables pthread_mutex_t LOCK_delete; // Locked before thd is deleted + /* + statement_name -> (Statement*) map of statements prepared using SQL syntax. + Hash element is SQL_PREP_STMT_ENTRY. + */ + HASH sql_prepared_stmts; + /* all prepared statements and cursors of this connection */ Statement_map stmt_map; /* @@ -1269,6 +1275,14 @@ class user_var_entry DTCollation collation; }; +class Prepared_statement; +/* Needed by THD::sql_prepared_stmts */ +typedef struct st_sql_prep_stmt_entry +{ + public: + LEX_STRING name; + Prepared_statement *stmt; +}SQL_PREP_STMT_ENTRY; /* Class for unique (removing of duplicates) */ diff --git a/sql/sql_lex.h b/sql/sql_lex.h index b9d85a23011..b1dd0355d62 100644 --- a/sql/sql_lex.h +++ b/sql/sql_lex.h @@ -76,6 +76,7 @@ enum enum_sql_command { SQLCOM_SHOW_COLUMN_TYPES, SQLCOM_SHOW_STORAGE_ENGINES, SQLCOM_SHOW_PRIVILEGES, SQLCOM_HELP, SQLCOM_DROP_USER, SQLCOM_REVOKE_ALL, SQLCOM_CHECKSUM, + SQLCOM_PREPARE, SQLCOM_EXECUTE, SQLCOM_DEALLOCATE_PREPARE, /* This should be the last !!! */ SQLCOM_END }; @@ -583,6 +584,11 @@ typedef struct st_lex bool in_comment, ignore_space, verbose, simple_alter, no_write_to_binlog; bool derived_tables; bool safe_to_cache_query; + /* Prepared statements SQL syntax:*/ + LEX_STRING prepared_stmt_name; /* Statement name (in all queries) */ + LEX_STRING prepared_stmt_code; /* Statement query (in PREPARE )*/ + /* Names of user variables holding parameters (in EXECUTE) */ + List prepared_stmt_params; st_lex() {} inline void uncacheable(uint8 cause) { diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index 68ef195cdc4..f2c36eb5513 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -1956,7 +1956,91 @@ mysql_execute_command(THD *thd) } break; } - + case SQLCOM_PREPARE: + { + char *stmt_name= lex->prepared_stmt_name.str; + uint name_len= lex->prepared_stmt_name.length; + Prepared_statement *stmt; + SQL_PREP_STMT_ENTRY *entry; + DBUG_PRINT("info", ("PREPARE: %.*s FROM '%.*s' \n", name_len, stmt_name, + lex->prepared_stmt_code.length, + lex->prepared_stmt_code.str)); + if ((entry=(SQL_PREP_STMT_ENTRY*)hash_search(&thd->sql_prepared_stmts, + (byte*)stmt_name, name_len))) + { + /* Free the statement with the same name and reuse hash entry */ + thd->stmt_map.erase((Statement*)entry->stmt); + } + else + { + uint size=ALIGN_SIZE(sizeof(SQL_PREP_STMT_ENTRY))+name_len+1; + if (!hash_inited(&thd->sql_prepared_stmts) || + !(entry= (SQL_PREP_STMT_ENTRY*)my_malloc(size,MYF(MY_WME)))) + { + send_error(thd, ER_OUT_OF_RESOURCES); + break; + } + entry->name.str= (char*)entry + ALIGN_SIZE(sizeof(SQL_PREP_STMT_ENTRY)); + entry->name.length= name_len; + memcpy(entry->name.str, stmt_name, name_len+1); + if (my_hash_insert(&thd->sql_prepared_stmts, (byte*)entry)) + { + my_free((char*)entry,MYF(0)); + send_error(thd, ER_OUT_OF_RESOURCES); + break; + } + } + /* Pretend this is a COM_PREPARE query so parser allows placeholders etc*/ + thd->command= COM_PREPARE; + /* 'length+1' is for alloc_query that strips the last character */ + stmt= mysql_stmt_prepare(thd, lex->prepared_stmt_code.str, + lex->prepared_stmt_code.length + 1, true); + if (stmt) + { + entry->stmt= stmt; + send_ok(thd, 0L, 0L, "Statement prepared"); + } + else + hash_delete(&thd->sql_prepared_stmts, (byte*)entry); + break; + } + case SQLCOM_EXECUTE: + { + char *stmt_name= lex->prepared_stmt_name.str; + uint name_len= lex->prepared_stmt_name.length; + SQL_PREP_STMT_ENTRY *entry; + DBUG_PRINT("info", ("EXECUTE: %.*s\n", name_len, stmt_name)); + + if (!(entry= (SQL_PREP_STMT_ENTRY*)hash_search(&thd->sql_prepared_stmts, + (byte*)stmt_name, + name_len))) + { + send_error(thd, ER_UNKNOWN_STMT_HANDLER, "Undefined prepared statement"); + lex->prepared_stmt_params.empty(); + break; + } + mysql_sql_stmt_execute(thd, entry->stmt); + lex->prepared_stmt_params.empty(); + break; + } + case SQLCOM_DEALLOCATE_PREPARE: + { + char *stmt_name= lex->prepared_stmt_name.str; + uint name_len= lex->prepared_stmt_name.length; + SQL_PREP_STMT_ENTRY *entry; + DBUG_PRINT("info", ("DEALLOCATE PREPARE: %.*s\n", name_len, stmt_name)); + if (!(entry= (SQL_PREP_STMT_ENTRY*)hash_search(&thd->sql_prepared_stmts, + (byte*)stmt_name, + name_len))) + { + send_error(thd, ER_UNKNOWN_STMT_HANDLER, "Undefined prepared statement"); + break; + } + thd->stmt_map.erase((Statement*)entry->stmt); + hash_delete(&thd->sql_prepared_stmts, (byte*)entry); + send_ok(thd); + break; + } case SQLCOM_DO: if (tables && ((res= check_table_access(thd, SELECT_ACL, tables,0)) || (res= open_and_lock_tables(thd,tables)))) diff --git a/sql/sql_prepare.cc b/sql/sql_prepare.cc index 0285c1eec2f..655285d263c 100644 --- a/sql/sql_prepare.cc +++ b/sql/sql_prepare.cc @@ -99,6 +99,8 @@ public: #else bool (*set_params_data)(Prepared_statement *st); #endif + bool (*set_params_from_vars)(Prepared_statement *stmt, + List& varnames); public: Prepared_statement(THD *thd_arg); virtual ~Prepared_statement(); @@ -623,6 +625,120 @@ static bool emb_insert_params_withlog(Prepared_statement *stmt) #endif /*!EMBEDDED_LIBRARY*/ + +/* + Set prepared statement parameters from user variables. + Also replace '?' marks with values in thd->query if binary logging is on. + SYNOPSIS + insert_params_from_vars() + stmt Statement + varnames List of variables. Caller must ensure that number of variables + in the list is equal to number of statement parameters + +*/ + +static bool insert_params_from_vars(Prepared_statement *stmt, + List& varnames) +{ + Item_param **begin= stmt->param_array; + Item_param **end= begin + stmt->param_count; + user_var_entry *entry; + LEX_STRING *varname; + DBUG_ENTER("insert_params_from_vars"); + + List_iterator var_it(varnames); + for (Item_param **it= begin; it < end; ++it) + { + Item_param *param= *it; + varname= var_it++; + if ((entry= (user_var_entry*)hash_search(&stmt->thd->user_vars, + (byte*) varname->str, + varname->length))) + { + param->item_result_type= entry->type; + switch (entry->type) + { + case REAL_RESULT: + param->set_double(*(double*)entry->value); + break; + case INT_RESULT: + param->set_int(*(longlong*)entry->value); + break; + case STRING_RESULT: + param->set_value(entry->value, entry->length, + entry->collation.collation); + break; + default: + DBUG_ASSERT(0); + } + } + else + { + param->item_result_type= INT_RESULT; + param->maybe_null= param->null_value= 1; + param->value_is_set= 0; + } + } + DBUG_RETURN(0); +} + +static bool insert_params_from_vars_with_log(Prepared_statement *stmt, + List& varnames) +{ + Item_param **begin= stmt->param_array; + Item_param **end= begin + stmt->param_count; + user_var_entry *entry; + LEX_STRING *varname; + DBUG_ENTER("insert_params_from_vars"); + + List_iterator var_it(varnames); + String str, query; + const String *res; + uint32 length= 0; + + for (Item_param **it= begin; it < end; ++it) + { + Item_param *param= *it; + varname= var_it++; + if ((entry= (user_var_entry*)hash_search(&stmt->thd->user_vars, + (byte*) varname->str, + varname->length))) + { + param->item_result_type= entry->type; + switch (entry->type) + { + case REAL_RESULT: + param->set_double(*(double*)entry->value); + break; + case INT_RESULT: + param->set_int(*(longlong*)entry->value); + break; + case STRING_RESULT: + param->set_value(entry->value, entry->length, + entry->collation.collation); + break; + default: + DBUG_ASSERT(0); + } + res= param->query_val_str(&str); + } + else + { + param->item_result_type= INT_RESULT; + param->maybe_null= param->null_value= 1; + param->value_is_set= 0; + res= &my_null_string; + } + + if (query.replace(param->pos_in_query+length, 1, *res)) + DBUG_RETURN(1); + length+= res->length()-1; + } + if (alloc_query(stmt->thd, (char *) query.ptr(), query.length()+1)) + DBUG_RETURN(1); + DBUG_RETURN(0); +} + /* Validate the following information for INSERT statement: - field existence @@ -780,7 +896,8 @@ static int mysql_test_select_fields(Prepared_statement *stmt, Item *having, ORDER *proc, ulong select_options, SELECT_LEX_UNIT *unit, - SELECT_LEX *select_lex) + SELECT_LEX *select_lex, + bool text_protocol) { THD *thd= stmt->thd; LEX *lex= stmt->lex; @@ -814,7 +931,7 @@ static int mysql_test_select_fields(Prepared_statement *stmt, if (lex->describe) { - if (send_prep_stmt(stmt, 0)) + if (!text_protocol && send_prep_stmt(stmt, 0)) goto err; } else @@ -834,14 +951,16 @@ static int mysql_test_select_fields(Prepared_statement *stmt, goto err_prep; } - if (send_prep_stmt(stmt, fields.elements) || - thd->protocol_simple.send_fields(&fields, 0) + if (!text_protocol) + { + if (send_prep_stmt(stmt, fields.elements) || + thd->protocol_simple.send_fields(&fields, 0) #ifndef EMBEDDED_LIBRARY - || net_flush(&thd->net) + || net_flush(&thd->net) #endif - ) - goto err_prep; - + ) + goto err_prep; + } unit->cleanup(); } thd->free_temporary_memory_pool_for_ps_preparing(); @@ -865,7 +984,7 @@ err: 1 error, sent to client */ -static int send_prepare_results(Prepared_statement *stmt) +static int send_prepare_results(Prepared_statement *stmt, bool text_protocol) { THD *thd= stmt->thd; LEX *lex= stmt->lex; @@ -905,7 +1024,8 @@ static int send_prepare_results(Prepared_statement *stmt) select_lex->having, (ORDER*)lex->proc_list.first, select_lex->options | thd->options, - &(lex->unit), select_lex))) + &(lex->unit), select_lex, + text_protocol))) goto error; /* Statement and field info has already been sent */ DBUG_RETURN(0); @@ -917,7 +1037,7 @@ static int send_prepare_results(Prepared_statement *stmt) */ break; } - DBUG_RETURN(send_prep_stmt(stmt, 0)); + DBUG_RETURN(text_protocol? 0: send_prep_stmt(stmt, 0)); error: if (res < 0) @@ -970,9 +1090,11 @@ static bool init_param_array(Prepared_statement *stmt) list in lex->param_array, so that a fast and direct retrieval can be made without going through all field items. + */ -void mysql_stmt_prepare(THD *thd, char *packet, uint packet_length) +Prepared_statement *mysql_stmt_prepare(THD *thd, char *packet, + uint packet_length, bool text_protocol) { LEX *lex; Prepared_statement *stmt= new Prepared_statement(thd); @@ -982,14 +1104,14 @@ void mysql_stmt_prepare(THD *thd, char *packet, uint packet_length) if (stmt == 0) { send_error(thd, ER_OUT_OF_RESOURCES); - DBUG_VOID_RETURN; + DBUG_RETURN(NULL); } if (thd->stmt_map.insert(stmt)) { delete stmt; send_error(thd, ER_OUT_OF_RESOURCES); - DBUG_VOID_RETURN; + DBUG_RETURN(NULL); } thd->stmt_backup.set_statement(thd); @@ -1006,7 +1128,7 @@ void mysql_stmt_prepare(THD *thd, char *packet, uint packet_length) /* Statement map deletes statement on erase */ thd->stmt_map.erase(stmt); send_error(thd, ER_OUT_OF_RESOURCES); - DBUG_VOID_RETURN; + DBUG_RETURN(NULL); } mysql_log.write(thd, COM_PREPARE, "%s", packet); @@ -1018,7 +1140,7 @@ void mysql_stmt_prepare(THD *thd, char *packet, uint packet_length) error= yyparse((void *)thd) || thd->is_fatal_error || init_param_array(stmt) || - send_prepare_results(stmt); + send_prepare_results(stmt, text_protocol); /* restore to WAIT_PRIOR: QUERY_PRIOR is set inside alloc_query */ if (!(specialflag & SPECIAL_NO_PRIOR)) @@ -1034,6 +1156,7 @@ void mysql_stmt_prepare(THD *thd, char *packet, uint packet_length) { /* Statement map deletes statement on erase */ thd->stmt_map.erase(stmt); + stmt= NULL; /* error is sent inside yyparse/send_prepare_results */ } else @@ -1048,7 +1171,7 @@ void mysql_stmt_prepare(THD *thd, char *packet, uint packet_length) sl->prep_where= sl->where; } } - DBUG_VOID_RETURN; + DBUG_RETURN(stmt); } /* Reinit statement before execution */ @@ -1109,7 +1232,6 @@ static void reset_stmt_for_execute(Prepared_statement *stmt) mysql_stmt_execute() */ - void mysql_stmt_execute(THD *thd, char *packet, uint packet_length) { ulong stmt_id= uint4korr(packet); @@ -1181,6 +1303,46 @@ set_params_data_err: } +/* + Execute prepared statement using parameter values from + lex->prepared_stmt_params and send result to the client using text protocol. +*/ + +void mysql_sql_stmt_execute(THD *thd, Prepared_statement *stmt) +{ + DBUG_ENTER("mysql_stmt_execute"); + if (stmt->param_count != thd->lex->prepared_stmt_params.elements) + { + my_error(ER_WRONG_ARGUMENTS, MYF(0), "mysql_execute"); + send_error(thd); + DBUG_VOID_RETURN; + } + thd->stmt_backup.set_statement(thd); + thd->set_statement(stmt); + reset_stmt_for_execute(stmt); + thd->command= COM_EXECUTE; + + if (stmt->set_params_from_vars(stmt, thd->stmt_backup.lex-> + prepared_stmt_params)) + { + thd->set_statement(&thd->stmt_backup); + my_error(ER_WRONG_ARGUMENTS, MYF(0), "mysql_execute"); + send_error(thd); + } + + if (!(specialflag & SPECIAL_NO_PRIOR)) + my_pthread_setprio(pthread_self(),QUERY_PRIOR); + mysql_execute_command(thd); + if (!(specialflag & SPECIAL_NO_PRIOR)) + my_pthread_setprio(pthread_self(), WAIT_PRIOR); + + cleanup_items(stmt->free_list); + close_thread_tables(thd); // to close derived tables + thd->set_statement(&thd->stmt_backup); + DBUG_VOID_RETURN; +} + + /* Reset a prepared statement, in case there was an error in send_longdata. Note: we don't send any reply to that command. @@ -1322,6 +1484,7 @@ Prepared_statement::Prepared_statement(THD *thd_arg) if (mysql_bin_log.is_open()) { log_full_query= 1; + set_params_from_vars= insert_params_from_vars_with_log; #ifndef EMBEDDED_LIBRARY set_params= insert_params_withlog; #else @@ -1329,11 +1492,14 @@ Prepared_statement::Prepared_statement(THD *thd_arg) #endif } else + { + set_params_from_vars= insert_params_from_vars; #ifndef EMBEDDED_LIBRARY set_params= insert_params; #else set_params_data= emb_insert_params; #endif + } } diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy index 90dc209f0bc..9e9b698f0b4 100644 --- a/sql/sql_yacc.yy +++ b/sql/sql_yacc.yy @@ -430,6 +430,8 @@ bool my_yyoverflow(short **a, YYSTYPE **b,int *yystacksize); %token MEDIUMTEXT %token NUMERIC_SYM %token PRECISION +%token PREPARE_SYM +%token DEALLOCATE_SYM %token QUICK %token REAL %token SIGNED_SYM @@ -722,6 +724,7 @@ bool my_yyoverflow(short **a, YYSTYPE **b,int *yystacksize); precision subselect_start opt_and charset subselect_end select_var_list select_var_list_init help opt_len opt_extended_describe + prepare execute deallocate END_OF_INPUT %type @@ -758,10 +761,12 @@ verb_clause: | checksum | commit | create + | deallocate | delete | describe | do | drop + | execute | flush | grant | handler @@ -773,6 +778,7 @@ verb_clause: | optimize | keycache | preload + | prepare | purge | rename | repair @@ -793,6 +799,72 @@ verb_clause: | use ; +deallocate: + DEALLOCATE_SYM PREPARE_SYM ident + { + THD *thd=YYTHD; + LEX *lex= thd->lex; + if (thd->command == COM_PREPARE) + { + yyerror(ER(ER_SYNTAX_ERROR)); + YYABORT; + } + lex->sql_command= SQLCOM_DEALLOCATE_PREPARE; + lex->prepared_stmt_name= $3; + }; + +prepare: + PREPARE_SYM ident FROM TEXT_STRING_sys + { + THD *thd=YYTHD; + LEX *lex= thd->lex; + if (thd->command == COM_PREPARE) + { + yyerror(ER(ER_SYNTAX_ERROR)); + YYABORT; + } + lex->sql_command= SQLCOM_PREPARE; + lex->prepared_stmt_name= $2; + lex->prepared_stmt_code= $4; + }; + + +execute: + EXECUTE_SYM ident + { + THD *thd=YYTHD; + LEX *lex= thd->lex; + if (thd->command == COM_PREPARE) + { + yyerror(ER(ER_SYNTAX_ERROR)); + YYABORT; + } + lex->sql_command= SQLCOM_EXECUTE; + lex->prepared_stmt_name= $2; + } + execute_using + {} + ; + +execute_using: + /* nothing */ + | USING execute_var_list + ; + +execute_var_list: + execute_var_list ',' execute_var_ident + | execute_var_ident + ; + +execute_var_ident: '@' ident_or_text + { + LEX *lex=Lex; + LEX_STRING *lexstr= (LEX_STRING*)sql_memdup(&$2, sizeof(LEX_STRING)); + if (!lexstr || lex->prepared_stmt_params.push_back(lexstr)) + YYABORT; + } + ; + /* help */ help: @@ -4782,6 +4854,7 @@ keyword: | DATETIME {} | DATE_SYM {} | DAY_SYM {} + | DEALLOCATE_SYM {} | DELAY_KEY_WRITE_SYM {} | DES_KEY_FILE {} | DIRECTORY_SYM {} @@ -4879,6 +4952,7 @@ keyword: | PASSWORD {} | POINT_SYM {} | POLYGON {} + | PREPARE_SYM {} | PREV_SYM {} | PROCESS {} | PROCESSLIST_SYM {} From ca75b62d5a44a1a44f9b68e8ab245d6e8fd84d6f Mon Sep 17 00:00:00 2001 From: "sergefp@mysql.com" <> Date: Wed, 7 Apr 2004 12:58:28 +0400 Subject: [PATCH 02/13] Correct handling of parameter variables with NULL values in PREPARE queries --- mysql-test/r/ps.result | 9 +++++++++ mysql-test/t/ps.test | 4 ++++ sql/sql_prepare.cc | 6 ++++-- 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/mysql-test/r/ps.result b/mysql-test/r/ps.result index 234c4af56f4..14af3c32292 100644 --- a/mysql-test/r/ps.result +++ b/mysql-test/r/ps.result @@ -66,6 +66,7 @@ NULL NULL NULL NULL +set @nullvar=1; set @nullvar=NULL; execute stmt5 using @nullvar; ? + a @@ -74,4 +75,12 @@ NULL NULL NULL NULL +set @nullvar2=NULL; +execute stmt5 using @nullvar2; +? + a +NULL +NULL +NULL +NULL +NULL drop table t1; diff --git a/mysql-test/t/ps.test b/mysql-test/t/ps.test index a97de1a0de7..ab698174161 100644 --- a/mysql-test/t/ps.test +++ b/mysql-test/t/ps.test @@ -68,8 +68,12 @@ execute stmt5 using @a; execute stmt5 using @no_such_var; +set @nullvar=1; set @nullvar=NULL; execute stmt5 using @nullvar; +set @nullvar2=NULL; +execute stmt5 using @nullvar2; + drop table t1; diff --git a/sql/sql_prepare.cc b/sql/sql_prepare.cc index 08691f67aa1..d9d19647c8c 100644 --- a/sql/sql_prepare.cc +++ b/sql/sql_prepare.cc @@ -661,7 +661,8 @@ static bool insert_params_from_vars(Prepared_statement *stmt, varname= var_it++; if ((entry= (user_var_entry*)hash_search(&stmt->thd->user_vars, (byte*) varname->str, - varname->length))) + varname->length)) + && entry->value) { param->item_result_type= entry->type; switch (entry->type) @@ -710,7 +711,8 @@ static bool insert_params_from_vars_with_log(Prepared_statement *stmt, varname= var_it++; if ((entry= (user_var_entry*)hash_search(&stmt->thd->user_vars, (byte*) varname->str, - varname->length))) + varname->length)) + && entry->value) { param->item_result_type= entry->type; switch (entry->type) From a314cbefa1d0cbf8d6d47438cded88b7eb7b29dc Mon Sep 17 00:00:00 2001 From: "sergefp@mysql.com" <> Date: Tue, 13 Apr 2004 01:58:48 +0400 Subject: [PATCH 03/13] WL#1622 "SQL Syntax for Prepared Statements": post-review fixes: Moved PS name to Statement class, Statement_map now handles name-to-statement resolution. Both named and unnamed statements are now executed in one function (sql_prepare.cc:execute_stmt) Fixed a problem: Malformed sequence of commands from client could cause server to use previously deleted objects. Some code cleanup and small fixes --- sql/mysql_priv.h | 7 ++- sql/sql_class.cc | 62 +++++++++++++++---------- sql/sql_class.h | 43 ++++++++--------- sql/sql_parse.cc | 91 +++++++++--------------------------- sql/sql_prepare.cc | 113 ++++++++++++++++++++++++++------------------- 5 files changed, 150 insertions(+), 166 deletions(-) diff --git a/sql/mysql_priv.h b/sql/mysql_priv.h index 2d65e8395ea..b24fa4f5cbd 100644 --- a/sql/mysql_priv.h +++ b/sql/mysql_priv.h @@ -613,11 +613,10 @@ int mysqld_show_column_types(THD *thd); int mysqld_help (THD *thd, const char *text); /* sql_prepare.cc */ -class Prepared_statement; -Prepared_statement *mysql_stmt_prepare(THD *thd, char *packet, - uint packet_length, bool text_protocol=false); +int mysql_stmt_prepare(THD *thd, char *packet, uint packet_length, + LEX_STRING *name=NULL); void mysql_stmt_execute(THD *thd, char *packet, uint packet_length); -void mysql_sql_stmt_execute(THD *thd, Prepared_statement *stmt); +void mysql_sql_stmt_execute(THD *thd, LEX_STRING *stmt_name); void mysql_stmt_free(THD *thd, char *packet); void mysql_stmt_reset(THD *thd, char *packet); void mysql_stmt_get_longdata(THD *thd, char *pos, ulong packet_length); diff --git a/sql/sql_class.cc b/sql/sql_class.cc index 49fa0455a30..87b6c49a4b7 100644 --- a/sql/sql_class.cc +++ b/sql/sql_class.cc @@ -78,24 +78,6 @@ extern "C" void free_user_var(user_var_entry *entry) my_free((char*) entry,MYF(0)); } -/**************************************************************************** -** SQL syntax names for Prepared Statements -****************************************************************************/ - -extern "C" byte *get_stmt_key(SQL_PREP_STMT_ENTRY *entry, uint *length, - my_bool not_used __attribute__((unused))) -{ - *length=(uint) entry->name.length; - return (byte*) entry->name.str; -} - -extern "C" void free_sql_stmt(SQL_PREP_STMT_ENTRY *entry) -{ - char *pos= (char*) entry+ALIGN_SIZE(sizeof(*entry)); - my_free((char*) entry,MYF(0)); -} - - /**************************************************************************** ** Thread specific functions ****************************************************************************/ @@ -178,9 +160,6 @@ THD::THD():user_time(0), current_statement(0), is_fatal_error(0), else bzero((char*) &user_var_events, sizeof(user_var_events)); - hash_init(&sql_prepared_stmts, &my_charset_bin, USER_VARS_HASH_SIZE, 0, 0, - (hash_get_key) get_stmt_key, - (hash_free_key) free_sql_stmt,0); /* Protocol */ protocol= &protocol_simple; // Default protocol protocol_simple.init(this); @@ -299,7 +278,6 @@ void THD::cleanup(void) my_free((char*) variables.datetime_format, MYF(MY_ALLOW_ZERO_PTR)); delete_dynamic(&user_var_events); hash_free(&user_vars); - hash_free(&sql_prepared_stmts); if (global_read_lock) unlock_global_read_lock(this); if (ull) @@ -1220,6 +1198,7 @@ Statement::Statement(THD *thd) query_length(0), free_list(0) { + name.str= NULL; init_sql_alloc(&mem_root, thd->variables.query_alloc_block_size, thd->variables.query_prealloc_size); @@ -1303,17 +1282,52 @@ static void delete_statement_as_hash_key(void *key) delete (Statement *) key; } +byte *get_stmt_name_hash_key(Statement *entry, uint *length, + my_bool not_used __attribute__((unused))) +{ + *length=(uint) entry->name.length; + return (byte*) entry->name.str; +} + C_MODE_END Statement_map::Statement_map() : last_found_statement(0) { - enum { START_HASH_SIZE = 16 }; - hash_init(&st_hash, default_charset_info, START_HASH_SIZE, 0, 0, + enum + { + START_STMT_HASH_SIZE = 16, + START_NAME_HASH_SIZE = 16 + }; + hash_init(&st_hash, default_charset_info, START_STMT_HASH_SIZE, 0, 0, get_statement_id_as_hash_key, delete_statement_as_hash_key, MYF(0)); + hash_init(&names_hash, &my_charset_bin, START_NAME_HASH_SIZE, 0, 0, + (hash_get_key) get_stmt_name_hash_key, + NULL,MYF(0)); } +int Statement_map::insert(Statement *statement) +{ + int rc= my_hash_insert(&st_hash, (byte *) statement); + if (rc == 0) + last_found_statement= statement; + if (statement->name.str) + { + /* + 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); + } + return rc; +} + + 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 4eb86b20337..8ccfe3cddd5 100644 --- a/sql/sql_class.h +++ b/sql/sql_class.h @@ -456,6 +456,7 @@ public: */ bool allow_sum_func; + LEX_STRING name; /* name for named prepared statements */ LEX *lex; // parse tree descriptor /* Points to the query associated with this statement. It's const, but @@ -522,8 +523,14 @@ public: /* - Used to seek all existing statements in the connection - Deletes all statements in destructor. + Container for all statements created/used in a connection. + Statements in Statement_map have unique Statement::id (guaranteed by id + assignment in Statement::Statement) + Non-empty statement names are unique too: attempt to insert a new statement + with duplicate name causes older statement to be deleted + + Statements are auto-deleted when they are removed from the map and when the + map is deleted. */ class Statement_map @@ -531,12 +538,14 @@ class Statement_map public: Statement_map(); - int insert(Statement *statement) + int insert(Statement *statement); + + Statement *find_by_name(LEX_STRING *name) { - int rc= my_hash_insert(&st_hash, (byte *) statement); - if (rc == 0) - last_found_statement= statement; - return rc; + Statement *stmt; + stmt= (Statement*)hash_search(&names_hash, (byte*)name->str, + name->length); + return stmt; } Statement *find(ulong id) @@ -550,15 +559,21 @@ public: { 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); } ~Statement_map() { hash_free(&st_hash); + hash_free(&names_hash); } private: HASH st_hash; + HASH names_hash; Statement *last_found_statement; }; @@ -594,12 +609,6 @@ public: struct system_variables variables; // Changeable local variables pthread_mutex_t LOCK_delete; // Locked before thd is deleted - /* - statement_name -> (Statement*) map of statements prepared using SQL syntax. - Hash element is SQL_PREP_STMT_ENTRY. - */ - HASH sql_prepared_stmts; - /* all prepared statements and cursors of this connection */ Statement_map stmt_map; /* @@ -1276,14 +1285,6 @@ class user_var_entry DTCollation collation; }; -class Prepared_statement; -/* Needed by THD::sql_prepared_stmts */ -typedef struct st_sql_prep_stmt_entry -{ - public: - LEX_STRING name; - Prepared_statement *stmt; -}SQL_PREP_STMT_ENTRY; /* Class for unique (removing of duplicates) */ diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index 91bcc9e0495..bdf6ac747c3 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -1960,88 +1960,41 @@ mysql_execute_command(THD *thd) break; } case SQLCOM_PREPARE: - { - char *stmt_name= lex->prepared_stmt_name.str; - uint name_len= lex->prepared_stmt_name.length; - Prepared_statement *stmt; - SQL_PREP_STMT_ENTRY *entry; - DBUG_PRINT("info", ("PREPARE: %.*s FROM '%.*s' \n", name_len, stmt_name, + { + DBUG_PRINT("info", ("PREPARE: %.*s FROM '%.*s' \n", + lex->prepared_stmt_name.length, + lex->prepared_stmt_name.str, lex->prepared_stmt_code.length, lex->prepared_stmt_code.str)); - if ((entry=(SQL_PREP_STMT_ENTRY*)hash_search(&thd->sql_prepared_stmts, - (byte*)stmt_name, name_len))) - { - /* Free the statement with the same name and reuse hash entry */ - thd->stmt_map.erase((Statement*)entry->stmt); - } - else - { - uint size=ALIGN_SIZE(sizeof(SQL_PREP_STMT_ENTRY))+name_len+1; - if (!hash_inited(&thd->sql_prepared_stmts) || - !(entry= (SQL_PREP_STMT_ENTRY*)my_malloc(size,MYF(MY_WME)))) - { - send_error(thd, ER_OUT_OF_RESOURCES); - break; - } - entry->name.str= (char*)entry + ALIGN_SIZE(sizeof(SQL_PREP_STMT_ENTRY)); - entry->name.length= name_len; - memcpy(entry->name.str, stmt_name, name_len+1); - if (my_hash_insert(&thd->sql_prepared_stmts, (byte*)entry)) - { - my_free((char*)entry,MYF(0)); - send_error(thd, ER_OUT_OF_RESOURCES); - break; - } - } - /* Pretend this is a COM_PREPARE query so parser allows placeholders etc*/ thd->command= COM_PREPARE; - /* 'length+1' is for alloc_query that strips the last character */ - stmt= mysql_stmt_prepare(thd, lex->prepared_stmt_code.str, - lex->prepared_stmt_code.length + 1, true); - if (stmt) - { - entry->stmt= stmt; + if (!mysql_stmt_prepare(thd, lex->prepared_stmt_code.str, + lex->prepared_stmt_code.length + 1, + &lex->prepared_stmt_name)) send_ok(thd, 0L, 0L, "Statement prepared"); - } - else - hash_delete(&thd->sql_prepared_stmts, (byte*)entry); break; } case SQLCOM_EXECUTE: { - char *stmt_name= lex->prepared_stmt_name.str; - uint name_len= lex->prepared_stmt_name.length; - SQL_PREP_STMT_ENTRY *entry; - DBUG_PRINT("info", ("EXECUTE: %.*s\n", name_len, stmt_name)); - - if (!(entry= (SQL_PREP_STMT_ENTRY*)hash_search(&thd->sql_prepared_stmts, - (byte*)stmt_name, - name_len))) - { - send_error(thd, ER_UNKNOWN_STMT_HANDLER, "Undefined prepared statement"); - lex->prepared_stmt_params.empty(); - break; - } - mysql_sql_stmt_execute(thd, entry->stmt); + DBUG_PRINT("info", ("EXECUTE: %.*s\n", + lex->prepared_stmt_name.length, + lex->prepared_stmt_name.str)); + mysql_sql_stmt_execute(thd, &lex->prepared_stmt_name); lex->prepared_stmt_params.empty(); break; } case SQLCOM_DEALLOCATE_PREPARE: { - char *stmt_name= lex->prepared_stmt_name.str; - uint name_len= lex->prepared_stmt_name.length; - SQL_PREP_STMT_ENTRY *entry; - DBUG_PRINT("info", ("DEALLOCATE PREPARE: %.*s\n", name_len, stmt_name)); - if (!(entry= (SQL_PREP_STMT_ENTRY*)hash_search(&thd->sql_prepared_stmts, - (byte*)stmt_name, - name_len))) + Statement* stmt; + DBUG_PRINT("info", ("DEALLOCATE PREPARE: %.*s\n", + lex->prepared_stmt_name.length, + lex->prepared_stmt_name.str)); + if ((stmt= thd->stmt_map.find_by_name(&lex->prepared_stmt_name))) { - send_error(thd, ER_UNKNOWN_STMT_HANDLER, "Undefined prepared statement"); - break; + thd->stmt_map.erase(stmt); + send_ok(thd); } - thd->stmt_map.erase((Statement*)entry->stmt); - hash_delete(&thd->sql_prepared_stmts, (byte*)entry); - send_ok(thd); + else + send_error(thd,ER_UNKNOWN_STMT_HANDLER,"Undefined prepared statement"); break; } case SQLCOM_DO: @@ -2259,9 +2212,9 @@ mysql_execute_command(THD *thd) tables= tables->next; // and from local list if it is not the same if (&lex->select_lex != lex->all_selects_list) - lex->select_lex.table_list.first= (gptr)create_table_local->next; + lex->select_lex.table_list.first= (gptr)create_table_local->next; else - lex->select_lex.table_list.first= (gptr)tables; + lex->select_lex.table_list.first= (gptr)tables; create_table->next= 0; ulong want_priv= ((lex->create_info.options & HA_LEX_CREATE_TMP_TABLE) ? diff --git a/sql/sql_prepare.cc b/sql/sql_prepare.cc index 51d75f07bd3..501d37e1383 100644 --- a/sql/sql_prepare.cc +++ b/sql/sql_prepare.cc @@ -107,6 +107,7 @@ public: virtual Statement::Type type() const; }; +static void execute_stmt(THD *thd, Prepared_statement *stmt); /****************************************************************************** Implementation @@ -636,7 +637,6 @@ static bool emb_insert_params_withlog(Prepared_statement *stmt) /* Set prepared statement parameters from user variables. - Also replace '?' marks with values in thd->query if binary logging is on. SYNOPSIS insert_params_from_vars() stmt Statement @@ -682,11 +682,7 @@ static bool insert_params_from_vars(Prepared_statement *stmt, } } else - { - param->item_result_type= INT_RESULT; - param->maybe_null= param->null_value= 1; - param->value_is_set= 0; - } + param->maybe_null= param->null_value= param->value_is_set= 1; } DBUG_RETURN(0); } @@ -704,6 +700,8 @@ static bool insert_params_from_vars_with_log(Prepared_statement *stmt, String str, query; const String *res; uint32 length= 0; + if (query.copy(stmt->query, stmt->query_length, default_charset_info)) + DBUG_RETURN(1); for (Item_param **it= begin; it < end; ++it) { @@ -734,9 +732,7 @@ static bool insert_params_from_vars_with_log(Prepared_statement *stmt, } else { - param->item_result_type= INT_RESULT; - param->maybe_null= param->null_value= 1; - param->value_is_set= 0; + param->maybe_null= param->null_value= param->value_is_set= 1; res= &my_null_string; } @@ -1089,6 +1085,14 @@ static bool init_param_array(Prepared_statement *stmt) /* + SYNOPSIS + mysql_stmt_prepare() + packet Prepared query + packet_length query length, with ignored trailing NULL or quote char. + name NULL or statement name. For unnamed statements binary PS + protocol is used, for named statmenents text protocol is + used. + Parse the query and send the total number of parameters and resultset metadata information back to client (if any), without executing the query i.e. without any log/disk @@ -1100,11 +1104,11 @@ static bool init_param_array(Prepared_statement *stmt) list in lex->param_array, so that a fast and direct retrieval can be made without going through all field items. - + */ -Prepared_statement *mysql_stmt_prepare(THD *thd, char *packet, - uint packet_length, bool text_protocol) +int mysql_stmt_prepare(THD *thd, char *packet, uint packet_length, + LEX_STRING *name) { LEX *lex; Prepared_statement *stmt= new Prepared_statement(thd); @@ -1116,14 +1120,26 @@ Prepared_statement *mysql_stmt_prepare(THD *thd, char *packet, if (stmt == 0) { send_error(thd, ER_OUT_OF_RESOURCES); - DBUG_RETURN(NULL); + DBUG_RETURN(1); + } + + if (name) + { + stmt->name.length= name->length; + if (!(stmt->name.str= my_memdup((byte*)name->str, name->length, + MYF(MY_WME)))) + { + delete stmt; + send_error(thd, ER_OUT_OF_RESOURCES); + DBUG_RETURN(1); + } } if (thd->stmt_map.insert(stmt)) { delete stmt; send_error(thd, ER_OUT_OF_RESOURCES); - DBUG_RETURN(NULL); + DBUG_RETURN(1); } thd->stmt_backup.set_statement(thd); @@ -1140,7 +1156,7 @@ Prepared_statement *mysql_stmt_prepare(THD *thd, char *packet, /* Statement map deletes statement on erase */ thd->stmt_map.erase(stmt); send_error(thd, ER_OUT_OF_RESOURCES); - DBUG_RETURN(NULL); + DBUG_RETURN(1); } mysql_log.write(thd, COM_PREPARE, "%s", packet); @@ -1152,7 +1168,7 @@ Prepared_statement *mysql_stmt_prepare(THD *thd, char *packet, error= yyparse((void *)thd) || thd->is_fatal_error || init_param_array(stmt) || - send_prepare_results(stmt, text_protocol); + send_prepare_results(stmt, test(name)); /* restore to WAIT_PRIOR: QUERY_PRIOR is set inside alloc_query */ if (!(specialflag & SPECIAL_NO_PRIOR)) @@ -1183,7 +1199,7 @@ Prepared_statement *mysql_stmt_prepare(THD *thd, char *packet, sl->prep_where= sl->where; } } - DBUG_RETURN(stmt); + DBUG_RETURN(!stmt); } /* Reinit statement before execution */ @@ -1236,6 +1252,7 @@ static void reset_stmt_for_execute(Prepared_statement *stmt) } } + /* Executes previously prepared query. If there is any parameters, then replace markers with the data supplied @@ -1267,11 +1284,6 @@ void mysql_stmt_execute(THD *thd, char *packet, uint packet_length) DBUG_VOID_RETURN; } - thd->stmt_backup.set_statement(thd); - thd->set_statement(stmt); - - reset_stmt_for_execute(stmt); - #ifndef EMBEDDED_LIBRARY if (stmt->param_count) { @@ -1289,30 +1301,12 @@ void mysql_stmt_execute(THD *thd, char *packet, uint packet_length) if (stmt->param_count && stmt->set_params_data(stmt)) goto set_params_data_err; #endif - - if (!(specialflag & SPECIAL_NO_PRIOR)) - my_pthread_setprio(pthread_self(),QUERY_PRIOR); - - /* - TODO: - Also, have checks on basic executions such as mysql_insert(), - mysql_delete(), mysql_update() and mysql_select() to not to - have re-check on setup_* and other things .. - */ thd->protocol= &thd->protocol_prep; // Switch to binary protocol - mysql_execute_command(thd); + execute_stmt(thd, stmt); thd->protocol= &thd->protocol_simple; // Use normal protocol - - if (!(specialflag & SPECIAL_NO_PRIOR)) - my_pthread_setprio(pthread_self(), WAIT_PRIOR); - - cleanup_items(stmt->free_list); - close_thread_tables(thd); // to close derived tables - thd->set_statement(&thd->stmt_backup); DBUG_VOID_RETURN; set_params_data_err: - thd->set_statement(&thd->stmt_backup); my_error(ER_WRONG_ARGUMENTS, MYF(0), "mysql_execute"); send_error(thd); DBUG_VOID_RETURN; @@ -1324,28 +1318,48 @@ set_params_data_err: lex->prepared_stmt_params and send result to the client using text protocol. */ -void mysql_sql_stmt_execute(THD *thd, Prepared_statement *stmt) +void mysql_sql_stmt_execute(THD *thd, LEX_STRING *stmt_name) { + Prepared_statement *stmt; DBUG_ENTER("mysql_stmt_execute"); + + if (!(stmt= (Prepared_statement*)thd->stmt_map.find_by_name(stmt_name))) + { + send_error(thd, ER_UNKNOWN_STMT_HANDLER, + "Undefined prepared statement"); + DBUG_VOID_RETURN; + } + if (stmt->param_count != thd->lex->prepared_stmt_params.elements) { my_error(ER_WRONG_ARGUMENTS, MYF(0), "mysql_execute"); send_error(thd); DBUG_VOID_RETURN; } - thd->stmt_backup.set_statement(thd); - thd->set_statement(stmt); - reset_stmt_for_execute(stmt); + /* Item_param allows setting parameters in COM_EXECUTE only */ thd->command= COM_EXECUTE; - if (stmt->set_params_from_vars(stmt, thd->stmt_backup.lex-> - prepared_stmt_params)) + if (stmt->set_params_from_vars(stmt, thd->lex->prepared_stmt_params)) { - thd->set_statement(&thd->stmt_backup); my_error(ER_WRONG_ARGUMENTS, MYF(0), "mysql_execute"); send_error(thd); } + execute_stmt(thd, stmt); + DBUG_VOID_RETURN; +} + +/* + Execute prepared statement. + Caller must set parameter values and thd::protocol. +*/ +static void execute_stmt(THD *thd, Prepared_statement *stmt) +{ + DBUG_ENTER("execute_stmt"); + thd->stmt_backup.set_statement(thd); + thd->set_statement(stmt); + reset_stmt_for_execute(stmt); + if (!(specialflag & SPECIAL_NO_PRIOR)) my_pthread_setprio(pthread_self(),QUERY_PRIOR); mysql_execute_command(thd); @@ -1359,6 +1373,7 @@ void mysql_sql_stmt_execute(THD *thd, Prepared_statement *stmt) } + /* Reset a prepared statement, in case there was an error in send_longdata. Note: we don't send any reply to that command. @@ -1522,6 +1537,8 @@ Prepared_statement::Prepared_statement(THD *thd_arg) Prepared_statement::~Prepared_statement() { free_items(free_list); + if (name.str) + my_free(name.str, MYF(0)); } From 42c00f2aede8e80801b6bc96844304e39f5f2788 Mon Sep 17 00:00:00 2001 From: "sergefp@mysql.com" <> Date: Wed, 14 Apr 2004 22:20:19 +0400 Subject: [PATCH 04/13] Post-merge fixes --- sql/sql_prepare.cc | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/sql/sql_prepare.cc b/sql/sql_prepare.cc index e578dc988f8..d468fce1af6 100644 --- a/sql/sql_prepare.cc +++ b/sql/sql_prepare.cc @@ -1628,7 +1628,6 @@ void mysql_stmt_execute(THD *thd, char *packet, uint packet_length) #endif thd->protocol= &thd->protocol_prep; // Switch to binary protocol execute_stmt(thd, stmt); - thd->lex->unit.cleanup(); thd->protocol= &thd->protocol_simple; // Use normal protocol DBUG_VOID_RETURN; @@ -1670,7 +1669,6 @@ void mysql_sql_stmt_execute(THD *thd, LEX_STRING *stmt_name) my_error(ER_WRONG_ARGUMENTS, MYF(0), "mysql_execute"); send_error(thd); } - execute_stmt(thd, stmt); DBUG_VOID_RETURN; } @@ -1689,6 +1687,7 @@ static void execute_stmt(THD *thd, Prepared_statement *stmt) if (!(specialflag & SPECIAL_NO_PRIOR)) my_pthread_setprio(pthread_self(),QUERY_PRIOR); mysql_execute_command(thd); + thd->lex->unit.cleanup(); if (!(specialflag & SPECIAL_NO_PRIOR)) my_pthread_setprio(pthread_self(), WAIT_PRIOR); From 9a4acccd05e1013b06631f58e8e42b7953757ace Mon Sep 17 00:00:00 2001 From: "sergefp@mysql.com" <> Date: Fri, 30 Apr 2004 20:08:38 +0400 Subject: [PATCH 05/13] WL#1622 "SQL Syntax for Prepared Statements" - cosmetic code review fixes --- mysql-test/r/ps.result | 6 ++++++ mysql-test/t/ps.test | 11 +++++++++++ sql/sql_class.cc | 8 ++++---- sql/sql_yacc.yy | 19 +++++++++---------- 4 files changed, 30 insertions(+), 14 deletions(-) diff --git a/mysql-test/r/ps.result b/mysql-test/r/ps.result index 14af3c32292..d16f24b34c6 100644 --- a/mysql-test/r/ps.result +++ b/mysql-test/r/ps.result @@ -83,4 +83,10 @@ NULL NULL NULL NULL +prepare stmt6 from 'select 1; select2'; +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 '; select2' at line 1 +prepare stmt6 from 'insert into t1 values (5,"five"); select2'; +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 '; select2' at line 1 +explain prepare stmt6 from 'insert into t1 values (5,"five"); select2'; +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 'from 'insert into t1 values (5,"five"); select2'' at line 1 drop table t1; diff --git a/mysql-test/t/ps.test b/mysql-test/t/ps.test index ab698174161..dc9f054da0d 100644 --- a/mysql-test/t/ps.test +++ b/mysql-test/t/ps.test @@ -75,5 +75,16 @@ execute stmt5 using @nullvar; set @nullvar2=NULL; execute stmt5 using @nullvar2; +# Check that multiple SQL statements are disabled inside PREPARE +--error 1064 +prepare stmt6 from 'select 1; select2'; + +--error 1064 +prepare stmt6 from 'insert into t1 values (5,"five"); select2'; + +# This shouldn't parse +--error 1064 +explain prepare stmt6 from 'insert into t1 values (5,"five"); select2'; + drop table t1; diff --git a/sql/sql_class.cc b/sql/sql_class.cc index 87b6c49a4b7..bf2dbb3fc5c 100644 --- a/sql/sql_class.cc +++ b/sql/sql_class.cc @@ -1282,8 +1282,8 @@ static void delete_statement_as_hash_key(void *key) delete (Statement *) key; } -byte *get_stmt_name_hash_key(Statement *entry, uint *length, - my_bool not_used __attribute__((unused))) +static byte *get_stmt_name_hash_key(Statement *entry, uint *length, + my_bool not_used __attribute__((unused))) { *length=(uint) entry->name.length; return (byte*) entry->name.str; @@ -1303,8 +1303,8 @@ Statement_map::Statement_map() : get_statement_id_as_hash_key, delete_statement_as_hash_key, MYF(0)); hash_init(&names_hash, &my_charset_bin, START_NAME_HASH_SIZE, 0, 0, - (hash_get_key) get_stmt_name_hash_key, - NULL,MYF(0)); + (hash_get_key) get_stmt_name_hash_key, + NULL,MYF(0)); } int Statement_map::insert(Statement *statement) diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy index 247bec84e8e..afd461e0383 100644 --- a/sql/sql_yacc.yy +++ b/sql/sql_yacc.yy @@ -805,13 +805,13 @@ deallocate: DEALLOCATE_SYM PREPARE_SYM ident { THD *thd=YYTHD; - LEX *lex= thd->lex; + LEX *lex= thd->lex; if (thd->command == COM_PREPARE) { yyerror(ER(ER_SYNTAX_ERROR)); YYABORT; } - lex->sql_command= SQLCOM_DEALLOCATE_PREPARE; + lex->sql_command= SQLCOM_DEALLOCATE_PREPARE; lex->prepared_stmt_name= $3; }; @@ -819,29 +819,28 @@ prepare: PREPARE_SYM ident FROM TEXT_STRING_sys { THD *thd=YYTHD; - LEX *lex= thd->lex; + LEX *lex= thd->lex; if (thd->command == COM_PREPARE) { yyerror(ER(ER_SYNTAX_ERROR)); YYABORT; } - lex->sql_command= SQLCOM_PREPARE; + lex->sql_command= SQLCOM_PREPARE; lex->prepared_stmt_name= $2; lex->prepared_stmt_code= $4; }; - execute: EXECUTE_SYM ident { THD *thd=YYTHD; - LEX *lex= thd->lex; + LEX *lex= thd->lex; if (thd->command == COM_PREPARE) { yyerror(ER(ER_SYNTAX_ERROR)); YYABORT; } - lex->sql_command= SQLCOM_EXECUTE; + lex->sql_command= SQLCOM_EXECUTE; lex->prepared_stmt_name= $2; } execute_using @@ -854,8 +853,8 @@ execute_using: ; execute_var_list: - execute_var_list ',' execute_var_ident - | execute_var_ident + execute_var_list ',' execute_var_ident + | execute_var_ident ; execute_var_ident: '@' ident_or_text @@ -864,7 +863,7 @@ execute_var_ident: '@' ident_or_text LEX_STRING *lexstr= (LEX_STRING*)sql_memdup(&$2, sizeof(LEX_STRING)); if (!lexstr || lex->prepared_stmt_params.push_back(lexstr)) YYABORT; - } + } ; /* help */ From 1eb588215bfb29eb2d7ea02b6ce1741b0eb996a0 Mon Sep 17 00:00:00 2001 From: "sergefp@mysql.com" <> Date: Fri, 30 Apr 2004 20:44:46 +0400 Subject: [PATCH 06/13] More small WL#1622 fixes: Allocate name of Prepared Statement on PS's mem_root. --- sql/sql_prepare.cc | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/sql/sql_prepare.cc b/sql/sql_prepare.cc index efc5b9bdbf8..739b09cf958 100644 --- a/sql/sql_prepare.cc +++ b/sql/sql_prepare.cc @@ -1463,8 +1463,8 @@ int mysql_stmt_prepare(THD *thd, char *packet, uint packet_length, if (name) { stmt->name.length= name->length; - if (!(stmt->name.str= my_memdup((byte*)name->str, name->length, - MYF(MY_WME)))) + if (!(stmt->name.str= memdup_root(&stmt->mem_root, (byte*)name->str, + name->length))) { delete stmt; send_error(thd, ER_OUT_OF_RESOURCES); @@ -1874,8 +1874,6 @@ Prepared_statement::Prepared_statement(THD *thd_arg) Prepared_statement::~Prepared_statement() { free_items(free_list); - if (name.str) - my_free(name.str, MYF(0)); } From 963ae8a9a7caa1f24047535b96cec480352cdc3e Mon Sep 17 00:00:00 2001 From: "sergefp@mysql.com" <> Date: Wed, 5 May 2004 19:11:40 +0400 Subject: [PATCH 07/13] WL#1622: Post-merge fixes --- sql/sql_parse.cc | 1 - sql/sql_prepare.cc | 16 ++++++++-------- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index 0c79dc6b743..b4ef30dbd0c 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -1424,7 +1424,6 @@ bool dispatch_command(enum enum_server_command command, THD *thd, } case COM_EXECUTE: { - thd->free_list= NULL; mysql_stmt_execute(thd, packet, packet_length); break; } diff --git a/sql/sql_prepare.cc b/sql/sql_prepare.cc index eb995a8369f..d1448e62013 100644 --- a/sql/sql_prepare.cc +++ b/sql/sql_prepare.cc @@ -1654,17 +1654,10 @@ void mysql_stmt_execute(THD *thd, char *packet, uint packet_length) thd->protocol= &thd->protocol_prep; // Switch to binary protocol execute_stmt(thd, stmt); thd->protocol= &thd->protocol_simple; // Use normal protocol - //psergey-todo: move this into execute_stmt: - reset_stmt_params(stmt); - /* - Free Items that were created during this execution of the PS by query - optimizer. - */ - free_items(thd->free_list); DBUG_VOID_RETURN; set_params_data_err: - reset_stmt_params(stmt); //psergey-todo: check if this belongs here + reset_stmt_params(stmt); my_error(ER_WRONG_ARGUMENTS, MYF(0), "mysql_execute"); send_error(thd); DBUG_VOID_RETURN; @@ -1709,10 +1702,12 @@ void mysql_sql_stmt_execute(THD *thd, LEX_STRING *stmt_name) /* Execute prepared statement. Caller must set parameter values and thd::protocol. + thd->free_list is assumed to be garbage. */ static void execute_stmt(THD *thd, Prepared_statement *stmt) { DBUG_ENTER("execute_stmt"); + thd->free_list= NULL; thd->stmt_backup.set_statement(thd); thd->set_statement(stmt); reset_stmt_for_execute(stmt); @@ -1724,6 +1719,11 @@ static void execute_stmt(THD *thd, Prepared_statement *stmt) if (!(specialflag & SPECIAL_NO_PRIOR)) my_pthread_setprio(pthread_self(), WAIT_PRIOR); + /* + Free Items that were created during this execution of the PS by query + optimizer. + */ + free_items(thd->free_list); cleanup_items(stmt->free_list); reset_stmt_params(stmt); close_thread_tables(thd); // to close derived tables From 7084c77651593b437e26d2f273bf2fe1ffdd9305 Mon Sep 17 00:00:00 2001 From: "sergefp@mysql.com" <> Date: Fri, 7 May 2004 03:32:51 +0400 Subject: [PATCH 08/13] Added a test for a problem that was fixed by automerge and fixed a typo. --- mysql-test/r/ps.result | 13 +++++++++++-- mysql-test/t/ps.test | 16 ++++++++++++++-- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/mysql-test/r/ps.result b/mysql-test/r/ps.result index d16f24b34c6..fc82645840c 100644 --- a/mysql-test/r/ps.result +++ b/mysql-test/r/ps.result @@ -2,7 +2,7 @@ drop table if exists t1,t2; create table t1 ( a int primary key, -b char(10), +b char(10) ); insert into t1 values (1,'one'); insert into t1 values (2,'two'); @@ -89,4 +89,13 @@ prepare stmt6 from 'insert into t1 values (5,"five"); select2'; 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 '; select2' at line 1 explain prepare stmt6 from 'insert into t1 values (5,"five"); select2'; 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 'from 'insert into t1 values (5,"five"); select2'' at line 1 -drop table t1; +create table t2 +( +a int +); +insert into t2 values (0); +set @arg00=NULL ; +prepare stmt1 from 'select 1 FROM t2 where a=?' ; +execute stmt1 using @arg00 ; +1 +drop table t1,t2; diff --git a/mysql-test/t/ps.test b/mysql-test/t/ps.test index dc9f054da0d..989dc6026fe 100644 --- a/mysql-test/t/ps.test +++ b/mysql-test/t/ps.test @@ -8,7 +8,7 @@ drop table if exists t1,t2; create table t1 ( a int primary key, - b char(10), + b char(10) ); insert into t1 values (1,'one'); insert into t1 values (2,'two'); @@ -86,5 +86,17 @@ prepare stmt6 from 'insert into t1 values (5,"five"); select2'; --error 1064 explain prepare stmt6 from 'insert into t1 values (5,"five"); select2'; -drop table t1; +create table t2 +( + a int +); + +insert into t2 values (0); + +# parameter is NULL +set @arg00=NULL ; +prepare stmt1 from 'select 1 FROM t2 where a=?' ; +execute stmt1 using @arg00 ; + +drop table t1,t2; From fb17025b3ea4983feec4498ab1f807b22a95d099 Mon Sep 17 00:00:00 2001 From: "sergefp@mysql.com" <> Date: Fri, 21 May 2004 04:27:50 +0400 Subject: [PATCH 09/13] WL#1622 "SQL Syntax for Prepared Statements": Post-review fixes (1 of 2) --- mysql-test/r/ps.result | 16 ++++- mysql-test/t/ps.test | 17 ++++++ mysys/my_error.c | 55 +++++++++++++---- sql/item.cc | 7 +-- sql/item.h | 3 +- sql/mysqld.cc | 9 ++- sql/share/czech/errmsg.txt | 2 +- sql/share/dutch/errmsg.txt | 2 +- sql/share/english/errmsg.txt | 2 +- sql/share/estonian/errmsg.txt | 2 +- sql/share/french/errmsg.txt | 2 +- sql/share/german/errmsg.txt | 2 +- sql/share/greek/errmsg.txt | 2 +- sql/share/hungarian/errmsg.txt | 2 +- sql/share/italian/errmsg.txt | 2 +- sql/share/japanese/errmsg.txt | 2 +- sql/share/korean/errmsg.txt | 2 +- sql/share/norwegian-ny/errmsg.txt | 2 +- sql/share/norwegian/errmsg.txt | 2 +- sql/share/polish/errmsg.txt | 2 +- sql/share/portuguese/errmsg.txt | 2 +- sql/share/romanian/errmsg.txt | 2 +- sql/share/russian/errmsg.txt | 2 +- sql/share/slovak/errmsg.txt | 2 +- sql/share/spanish/errmsg.txt | 2 +- sql/share/swedish/errmsg.txt | 2 +- sql/share/ukrainian/errmsg.txt | 2 +- sql/sql_class.h | 9 ++- sql/sql_lex.h | 8 ++- sql/sql_parse.cc | 99 ++++++++++++++++++++++++++++--- sql/sql_prepare.cc | 50 +++++++++------- sql/sql_yacc.yy | 21 ++++++- 32 files changed, 257 insertions(+), 79 deletions(-) diff --git a/mysql-test/r/ps.result b/mysql-test/r/ps.result index fc82645840c..6c228327b8d 100644 --- a/mysql-test/r/ps.result +++ b/mysql-test/r/ps.result @@ -21,7 +21,7 @@ a b 2 two 3 three deallocate prepare no_such_statement; -ERROR HY000: Undefined prepared statement +ERROR HY000: Unknown prepared statement handler (no_such_statement) given to DEALLOCATE PREPARE execute stmt1; ERROR HY000: Wrong arguments to mysql_execute prepare stmt2 from 'prepare nested_stmt from "select 1"'; @@ -98,4 +98,18 @@ set @arg00=NULL ; prepare stmt1 from 'select 1 FROM t2 where a=?' ; execute stmt1 using @arg00 ; 1 +prepare stmt1 from @nosuchvar; +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 'NULL' at line 1 +set @ivar= 1234; +prepare stmt1 from @ivar; +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 '1234' at line 1 +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 +set @str1 = 'select ?'; +set @str2 = convert(@str1 using ucs2); +prepare stmt1 from @str2; +execute stmt1 using @ivar; +? +1234 drop table t1,t2; diff --git a/mysql-test/t/ps.test b/mysql-test/t/ps.test index 989dc6026fe..d9e0f0852c5 100644 --- a/mysql-test/t/ps.test +++ b/mysql-test/t/ps.test @@ -98,5 +98,22 @@ set @arg00=NULL ; prepare stmt1 from 'select 1 FROM t2 where a=?' ; execute stmt1 using @arg00 ; +# prepare using variables: +--error 1064 +prepare stmt1 from @nosuchvar; + +set @ivar= 1234; +--error 1064 +prepare stmt1 from @ivar; + +set @fvar= 123.4567; +--error 1064 +prepare stmt1 from @fvar; + +set @str1 = 'select ?'; +set @str2 = convert(@str1 using ucs2); +prepare stmt1 from @str2; +execute stmt1 using @ivar; + drop table t1,t2; diff --git a/mysys/my_error.c b/mysys/my_error.c index 6fd346c89f7..7ca7dbae8de 100644 --- a/mysys/my_error.c +++ b/mysys/my_error.c @@ -33,6 +33,12 @@ char NEAR errbuff[NRERRBUFFS][ERRMSGSIZE]; nr Errno MyFlags Flags ... variable list + NOTE + The following subset of printf format is supported: + "%[0-9.-]*l?[sdu]", where all length flags are parsed but ignored. + + Additionally "%.*s" is supported and "%.*[ud]" is correctly parsed but + length value is ignored. */ int my_error(int nr,myf MyFlags, ...) @@ -43,7 +49,10 @@ int my_error(int nr,myf MyFlags, ...) reg2 char *endpos; char * par; char ebuff[ERRMSGSIZE+20]; + int prec_chars; + my_bool prec_supplied; DBUG_ENTER("my_error"); + LINT_INIT(prec_chars); /* protected by prec_supplied */ va_start(ap,MyFlags); DBUG_PRINT("my", ("nr: %d MyFlags: %d errno: %d", nr, MyFlags, errno)); @@ -59,7 +68,6 @@ int my_error(int nr,myf MyFlags, ...) if (tpos[0] != '%') { *endpos++= *tpos++; /* Copy ordinary char */ - olen++; continue; } if (*++tpos == '%') /* test if %% */ @@ -68,21 +76,48 @@ int my_error(int nr,myf MyFlags, ...) } else { - /* Skipp if max size is used (to be compatible with printf) */ - while (my_isdigit(&my_charset_latin1, *tpos) || *tpos == '.' || *tpos == '-') - tpos++; - if (*tpos == 'l') /* Skipp 'l' argument */ - tpos++; + /* + Skip size/precision flags to be compatible with printf. + The only size/precision flag supported is "%.*s". + "%.*u" and "%.*d" cause + */ + prec_supplied= 0; + if (*tpos== '.') + { + tpos++; + olen--; + if (*tpos == '*') + { + tpos++; + olen--; + prec_chars= va_arg(ap, int); /* get length parameter */ + prec_supplied= 1; + } + } + + if (!prec_supplied) + { + while (my_isdigit(&my_charset_latin1, *tpos) || *tpos == '.' || + *tpos == '-') + tpos++; + + if (*tpos == 'l') /* Skipp 'l' argument */ + tpos++; + } + if (*tpos == 's') /* String parameter */ { par = va_arg(ap, char *); plen = (uint) strlen(par); + if (prec_supplied && prec_chars > 0) + plen= min((uint)prec_chars, plen); if (olen + plen < ERRMSGSIZE+2) /* Replace if possible */ { - endpos=strmov(endpos,par); - tpos++; - olen+=plen-2; - continue; + memcpy(endpos,par, plen); + endpos += plen; + tpos++; + olen+=plen-2; + continue; } } else if (*tpos == 'd' || *tpos == 'u') /* Integer parameter */ diff --git a/sql/item.cc b/sql/item.cc index b6b99a9f717..8557820474d 100644 --- a/sql/item.cc +++ b/sql/item.cc @@ -253,7 +253,7 @@ bool Item::get_time(TIME *ltime) return 0; } -CHARSET_INFO * Item::default_charset() const +CHARSET_INFO * Item::default_charset() { return current_thd->variables.collation_connection; } @@ -678,11 +678,6 @@ void Item_param::set_value(const char *str, uint length, CHARSET_INFO *ci) DBUG_VOID_RETURN; } -void Item_param::set_value(const char *str, uint length) -{ - set_value(str, length, default_charset()); -} - void Item_param::set_time(TIME *tm, timestamp_type type) { diff --git a/sql/item.h b/sql/item.h index 471d502a6e1..062e1da990d 100644 --- a/sql/item.h +++ b/sql/item.h @@ -220,7 +220,7 @@ public: virtual Item *real_item() { return this; } virtual Item *get_tmp_table_item(THD *thd) { return copy_or_same(thd); } - CHARSET_INFO *default_charset() const; + static CHARSET_INFO *default_charset(); virtual CHARSET_INFO *compare_collation() { return NULL; } virtual bool walk(Item_processor processor, byte *arg) @@ -413,7 +413,6 @@ public: void set_null(); void set_int(longlong i); void set_double(double i); - void set_value(const char *str, uint length); void set_value(const char *str, uint length, CHARSET_INFO *ci); void set_long_str(const char *str, ulong length); void set_long_binary(const char *str, ulong length); diff --git a/sql/mysqld.cc b/sql/mysqld.cc index e810393af60..aa266861dd7 100644 --- a/sql/mysqld.cc +++ b/sql/mysqld.cc @@ -4842,9 +4842,12 @@ struct show_var_st status_vars[]= { {"Com_unlock_tables", (char*) (com_stat+(uint) SQLCOM_UNLOCK_TABLES),SHOW_LONG}, {"Com_update", (char*) (com_stat+(uint) SQLCOM_UPDATE),SHOW_LONG}, {"Com_update_multi", (char*) (com_stat+(uint) SQLCOM_UPDATE_MULTI),SHOW_LONG}, - {"Com_prepare_sql", (char*) (com_stat+(uint) SQLCOM_PREPARE), SHOW_LONG}, - {"Com_execute_sql", (char*) (com_stat+(uint) SQLCOM_EXECUTE), SHOW_LONG}, - {"Com_dealloc_sql", (char*) (com_stat+(uint) SQLCOM_DEALLOCATE_PREPARE), SHOW_LONG}, + {"Com_prepare_sql", (char*) (com_stat+(uint) SQLCOM_PREPARE), + SHOW_LONG}, + {"Com_execute_sql", (char*) (com_stat+(uint) SQLCOM_EXECUTE), + SHOW_LONG}, + {"Com_dealloc_sql", (char*) (com_stat+(uint) + SQLCOM_DEALLOCATE_PREPARE), SHOW_LONG}, {"Connections", (char*) &thread_id, SHOW_LONG_CONST}, {"Created_tmp_disk_tables", (char*) &created_tmp_disk_tables,SHOW_LONG}, {"Created_tmp_files", (char*) &my_tmp_file_created, SHOW_LONG}, diff --git a/sql/share/czech/errmsg.txt b/sql/share/czech/errmsg.txt index f3a0c5e0eec..f554a1171d3 100644 --- a/sql/share/czech/errmsg.txt +++ b/sql/share/czech/errmsg.txt @@ -255,7 +255,7 @@ character-set=latin2 "Key reference and table reference doesn't match", "Operand should contain %d column(s)", "Subquery returns more than 1 row", -"Unknown prepared statement handler (%ld) given to %s", +"Unknown prepared statement handler (%.*s) given to %s", "Help database is corrupt or does not exist", "Cyclic reference on subqueries", "Converting column '%s' from %s to %s", diff --git a/sql/share/dutch/errmsg.txt b/sql/share/dutch/errmsg.txt index 9d9dfb14a89..0db72a4fb22 100644 --- a/sql/share/dutch/errmsg.txt +++ b/sql/share/dutch/errmsg.txt @@ -257,7 +257,7 @@ character-set=latin1 "Key reference and table reference doesn't match", "Operand should contain %d column(s)", "Subquery returns more than 1 row", -"Unknown prepared statement handler (%ld) given to %s", +"Unknown prepared statement handler (%.*s) given to %s", "Help database is corrupt or does not exist", "Cyclic reference on subqueries", "Converting column '%s' from %s to %s", diff --git a/sql/share/english/errmsg.txt b/sql/share/english/errmsg.txt index 14a854fbafb..592c44dc3f4 100644 --- a/sql/share/english/errmsg.txt +++ b/sql/share/english/errmsg.txt @@ -246,7 +246,7 @@ character-set=latin1 "Key reference and table reference doesn't match", "Operand should contain %d column(s)", "Subquery returns more than 1 row", -"Unknown prepared statement handler (%ld) given to %s", +"Unknown prepared statement handler (%.*s) given to %s", "Help database is corrupt or does not exist", "Cyclic reference on subqueries", "Converting column '%s' from %s to %s", diff --git a/sql/share/estonian/errmsg.txt b/sql/share/estonian/errmsg.txt index 5d0f34fd4b2..caa0d8f039e 100644 --- a/sql/share/estonian/errmsg.txt +++ b/sql/share/estonian/errmsg.txt @@ -251,7 +251,7 @@ character-set=latin7 "Key reference and table reference doesn't match", "Operand should contain %d column(s)", "Subquery returns more than 1 row", -"Unknown prepared statement handler (%ld) given to %s", +"Unknown prepared statement handler (%.*s) given to %s", "Help database is corrupt or does not exist", "Cyclic reference on subqueries", "Converting column '%s' from %s to %s", diff --git a/sql/share/french/errmsg.txt b/sql/share/french/errmsg.txt index adc9f66e96b..fb4a2f0f5b1 100644 --- a/sql/share/french/errmsg.txt +++ b/sql/share/french/errmsg.txt @@ -246,7 +246,7 @@ character-set=latin1 "Key reference and table reference doesn't match", "Operand should contain %d column(s)", "Subquery returns more than 1 row", -"Unknown prepared statement handler (%ld) given to %s", +"Unknown prepared statement handler (%.*s) given to %s", "Help database is corrupt or does not exist", "Cyclic reference on subqueries", "Converting column '%s' from %s to %s", diff --git a/sql/share/german/errmsg.txt b/sql/share/german/errmsg.txt index 0b732ba48f8..6d484c36116 100644 --- a/sql/share/german/errmsg.txt +++ b/sql/share/german/errmsg.txt @@ -258,7 +258,7 @@ character-set=latin1 "Schlüssel- und Tabellenverweis passen nicht zusammen", "Operand solle %d Spalte(n) enthalten", "Unterabfrage lieferte mehr als einen Datensatz zurück", -"Unbekannter Prepared-Statement-Handler (%ld) für %s angegeben", +"Unbekannter Prepared-Statement-Handler (%.*s) für %s angegeben", "Die Hilfe-Datenbank ist beschädigt oder existiert nicht", "Zyklischer Verweis in Unterabfragen", "Spalte '%s' wird von %s nach %s umgewandelt", diff --git a/sql/share/greek/errmsg.txt b/sql/share/greek/errmsg.txt index f96c10b0e65..4bea8b2dfc6 100644 --- a/sql/share/greek/errmsg.txt +++ b/sql/share/greek/errmsg.txt @@ -246,7 +246,7 @@ character-set=greek "Key reference and table reference doesn't match", "Operand should contain %d column(s)", "Subquery returns more than 1 row", -"Unknown prepared statement handler (%ld) given to %s", +"Unknown prepared statement handler (%.*s) given to %s", "Help database is corrupt or does not exist", "Cyclic reference on subqueries", "Converting column '%s' from %s to %s", diff --git a/sql/share/hungarian/errmsg.txt b/sql/share/hungarian/errmsg.txt index a26790a4ef9..f7552d6fa15 100644 --- a/sql/share/hungarian/errmsg.txt +++ b/sql/share/hungarian/errmsg.txt @@ -248,7 +248,7 @@ character-set=latin2 "Key reference and table reference doesn't match", "Operand should contain %d column(s)", "Subquery returns more than 1 row", -"Unknown prepared statement handler (%ld) given to %s", +"Unknown prepared statement handler (%.*s) given to %s", "Help database is corrupt or does not exist", "Cyclic reference on subqueries", "Converting column '%s' from %s to %s", diff --git a/sql/share/italian/errmsg.txt b/sql/share/italian/errmsg.txt index 7c519e4e4bf..0a81a534fbc 100644 --- a/sql/share/italian/errmsg.txt +++ b/sql/share/italian/errmsg.txt @@ -246,7 +246,7 @@ character-set=latin1 "Key reference and table reference doesn't match", "Operand should contain %d column(s)", "Subquery returns more than 1 row", -"Unknown prepared statement handler (%ld) given to %s", +"Unknown prepared statement handler (%.*s) given to %s", "Help database is corrupt or does not exist", "Cyclic reference on subqueries", "Converting column '%s' from %s to %s", diff --git a/sql/share/japanese/errmsg.txt b/sql/share/japanese/errmsg.txt index f973f84d2a4..ce58d87871b 100644 --- a/sql/share/japanese/errmsg.txt +++ b/sql/share/japanese/errmsg.txt @@ -248,7 +248,7 @@ character-set=ujis "Key reference and table reference doesn't match", "Operand should contain %d column(s)", "Subquery returns more than 1 row", -"Unknown prepared statement handler (%ld) given to %s", +"Unknown prepared statement handler (%.*s) given to %s", "Help database is corrupt or does not exist", "Cyclic reference on subqueries", "Converting column '%s' from %s to %s", diff --git a/sql/share/korean/errmsg.txt b/sql/share/korean/errmsg.txt index 8b5d318ab19..6f3de30c6a5 100644 --- a/sql/share/korean/errmsg.txt +++ b/sql/share/korean/errmsg.txt @@ -246,7 +246,7 @@ character-set=euckr "Key reference and table reference doesn't match", "Operand should contain %d column(s)", "Subquery returns more than 1 row", -"Unknown prepared statement handler (%ld) given to %s", +"Unknown prepared statement handler (%.*s) given to %s", "Help database is corrupt or does not exist", "Cyclic reference on subqueries", "Converting column '%s' from %s to %s", diff --git a/sql/share/norwegian-ny/errmsg.txt b/sql/share/norwegian-ny/errmsg.txt index c0a7d736e1f..1ce737955af 100644 --- a/sql/share/norwegian-ny/errmsg.txt +++ b/sql/share/norwegian-ny/errmsg.txt @@ -248,7 +248,7 @@ character-set=latin1 "Key reference and table reference doesn't match", "Operand should contain %d column(s)", "Subquery returns more than 1 row", -"Unknown prepared statement handler (%ld) given to %s", +"Unknown prepared statement handler (%.*s) given to %s", "Help database is corrupt or does not exist", "Cyclic reference on subqueries", "Converting column '%s' from %s to %s", diff --git a/sql/share/norwegian/errmsg.txt b/sql/share/norwegian/errmsg.txt index fc9b5d2f6da..bb7afd5a8f4 100644 --- a/sql/share/norwegian/errmsg.txt +++ b/sql/share/norwegian/errmsg.txt @@ -248,7 +248,7 @@ character-set=latin1 "Key reference and table reference doesn't match", "Operand should contain %d column(s)", "Subquery returns more than 1 row", -"Unknown prepared statement handler (%ld) given to %s", +"Unknown prepared statement handler (%.*s) given to %s", "Help database is corrupt or does not exist", "Cyclic reference on subqueries", "Converting column '%s' from %s to %s", diff --git a/sql/share/polish/errmsg.txt b/sql/share/polish/errmsg.txt index 36b7d67d134..b4ff175822a 100644 --- a/sql/share/polish/errmsg.txt +++ b/sql/share/polish/errmsg.txt @@ -250,7 +250,7 @@ character-set=latin2 "Key reference and table reference doesn't match", "Operand should contain %d column(s)", "Subquery returns more than 1 row", -"Unknown prepared statement handler (%ld) given to %s", +"Unknown prepared statement handler (%.*s) given to %s", "Help database is corrupt or does not exist", "Cyclic reference on subqueries", "Converting column '%s' from %s to %s", diff --git a/sql/share/portuguese/errmsg.txt b/sql/share/portuguese/errmsg.txt index d4ffa2d5ef5..907ca6fc80c 100644 --- a/sql/share/portuguese/errmsg.txt +++ b/sql/share/portuguese/errmsg.txt @@ -247,7 +247,7 @@ character-set=latin1 "Referência da chave e referência da tabela não coincidem", "Operand should contain %d column(s)", "Subconsulta retorna mais que 1 registro", -"Desconhecido manipulador de declaração preparado (%ld) determinado para %s", +"Desconhecido manipulador de declaração preparado (%.*s) determinado para %s", "Banco de dado de ajuda corrupto ou não existente", "Referência cíclica em subconsultas", "Convertendo coluna '%s' de %s para %s", diff --git a/sql/share/romanian/errmsg.txt b/sql/share/romanian/errmsg.txt index 4918a6e1a10..cefd2074bf2 100644 --- a/sql/share/romanian/errmsg.txt +++ b/sql/share/romanian/errmsg.txt @@ -250,7 +250,7 @@ character-set=latin2 "Key reference and table reference doesn't match", "Operand should contain %d column(s)", "Subquery returns more than 1 row", -"Unknown prepared statement handler (%ld) given to %s", +"Unknown prepared statement handler (%.*s) given to %s", "Help database is corrupt or does not exist", "Cyclic reference on subqueries", "Converting column '%s' from %s to %s", diff --git a/sql/share/russian/errmsg.txt b/sql/share/russian/errmsg.txt index dbc93306a38..f8bf9ea10b2 100644 --- a/sql/share/russian/errmsg.txt +++ b/sql/share/russian/errmsg.txt @@ -248,7 +248,7 @@ character-set=koi8r "Key reference and table reference doesn't match", "ïÐÅÒÁÎÄ ÄÏÌÖÅÎ ÓÏÄÅÒÖÁÔØ %d ËÏÌÏÎÏË", "ðÏÄÚÁÐÒÏÓ ×ÏÚ×ÒÁÝÁÅÔ ÂÏÌÅÅ ÏÄÎÏÊ ÚÁÐÉÓÉ", -"Unknown prepared statement handler (%ld) given to %s", +"Unknown prepared statement handler (%.*s) given to %s", "Help database is corrupt or does not exist", "ãÉËÌÉÞÅÓËÁÑ ÓÓÙÌËÁ ÎÁ ÐÏÄÚÁÐÒÏÓ", "ðÒÅÏÂÒÁÚÏ×ÁÎÉÅ ÐÏÌÑ '%s' ÉÚ %s × %s", diff --git a/sql/share/slovak/errmsg.txt b/sql/share/slovak/errmsg.txt index 80d21f8e31f..16f9b0a8f35 100644 --- a/sql/share/slovak/errmsg.txt +++ b/sql/share/slovak/errmsg.txt @@ -254,7 +254,7 @@ character-set=latin2 "Key reference and table reference doesn't match", "Operand should contain %d column(s)", "Subquery returns more than 1 row", -"Unknown prepared statement handler (%ld) given to %s", +"Unknown prepared statement handler (%.*s) given to %s", "Help database is corrupt or does not exist", "Cyclic reference on subqueries", "Converting column '%s' from %s to %s", diff --git a/sql/share/spanish/errmsg.txt b/sql/share/spanish/errmsg.txt index 512f06c8c50..9ae998184e5 100644 --- a/sql/share/spanish/errmsg.txt +++ b/sql/share/spanish/errmsg.txt @@ -248,7 +248,7 @@ character-set=latin1 "Referencia de llave y referencia de tabla no coinciden", "Operando debe tener %d columna(s)", "Subconsulta retorna mas que 1 línea", -"Desconocido preparado comando handler (%ld) dado para %s", +"Desconocido preparado comando handler (%.*s) dado para %s", "Base de datos Help está corrupto o no existe", "Cíclica referencia en subconsultas", "Convirtiendo columna '%s' de %s para %s", diff --git a/sql/share/swedish/errmsg.txt b/sql/share/swedish/errmsg.txt index 22e7cb786b5..aed3e9d3d34 100644 --- a/sql/share/swedish/errmsg.txt +++ b/sql/share/swedish/errmsg.txt @@ -246,7 +246,7 @@ character-set=latin1 "Nyckelreferensen och tabellreferensen stämmer inte överens", "Operand should contain %d column(s)", "Subquery returnerade mer än 1 rad", -"Okänd PREPARED STATEMENT id (%ld) var given till %s", +"Okänd PREPARED STATEMENT id (%.*s) var given till %s", "Hjälpdatabasen finns inte eller är skadad", "Cyklisk referens i subqueries", "Konvertar kolumn '%s' från %s till %s", diff --git a/sql/share/ukrainian/errmsg.txt b/sql/share/ukrainian/errmsg.txt index 3149d58b413..c0ccd76f157 100644 --- a/sql/share/ukrainian/errmsg.txt +++ b/sql/share/ukrainian/errmsg.txt @@ -251,7 +251,7 @@ character-set=koi8u "Key reference and table reference doesn't match", "ïÐÅÒÁÎÄ ÍÁ¤ ÓËÌÁÄÁÔÉÓÑ Ú %d ÓÔÏ×Âæ×", "ð¦ÄÚÁÐÉÔ ÐÏ×ÅÒÔÁ¤ Â¦ÌØÛ ÎiÖ 1 ÚÁÐÉÓ", -"Unknown prepared statement handler (%ld) given to %s", +"Unknown prepared statement handler (%.*s) given to %s", "Help database is corrupt or does not exist", "ãÉË̦ÞÎÅ ÐÏÓÉÌÁÎÎÑ ÎÁ ЦÄÚÁÐÉÔ", "ðÅÒÅÔ×ÏÒÅÎÎÑ ÓÔÏ×ÂÃÁ '%s' Ú %s Õ %s", diff --git a/sql/sql_class.h b/sql/sql_class.h index f208a3f4d73..b5774688e1e 100644 --- a/sql/sql_class.h +++ b/sql/sql_class.h @@ -556,8 +556,13 @@ public: Statement *find(ulong id) { if (last_found_statement == 0 || id != last_found_statement->id) - last_found_statement= (Statement *) hash_search(&st_hash, (byte *) &id, - sizeof(id)); + { + Statement *stmt; + stmt= (Statement *) hash_search(&st_hash, (byte *) &id, sizeof(id)); + if (stmt->name.str) + return NULL; + last_found_statement= stmt; + } return last_found_statement; } void erase(Statement *statement) diff --git a/sql/sql_lex.h b/sql/sql_lex.h index a525bc485c1..9bea0de7b39 100644 --- a/sql/sql_lex.h +++ b/sql/sql_lex.h @@ -608,7 +608,13 @@ typedef struct st_lex bool safe_to_cache_query; /* Prepared statements SQL syntax:*/ LEX_STRING prepared_stmt_name; /* Statement name (in all queries) */ - LEX_STRING prepared_stmt_code; /* Statement query (in PREPARE )*/ + /* + Prepared statement query text or name of variable that holds the + prepared statement (in PREPARE ... queries) + */ + LEX_STRING prepared_stmt_code; + /* If true, prepared_stmt_code is a name of variable that holds the query */ + bool prepared_stmt_code_is_varref; /* Names of user variables holding parameters (in EXECUTE) */ List prepared_stmt_params; st_lex() {} diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index 65d538e3ac4..cd8891ad326 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -1972,14 +1972,90 @@ mysql_execute_command(THD *thd) } case SQLCOM_PREPARE: { - DBUG_PRINT("info", ("PREPARE: %.*s FROM '%.*s' \n", - lex->prepared_stmt_name.length, - lex->prepared_stmt_name.str, - lex->prepared_stmt_code.length, - lex->prepared_stmt_code.str)); + char *query_str; + uint query_len; + if (lex->prepared_stmt_code_is_varref) + { + /* This is PREPARE stmt FROM @var*/ + String str; + CHARSET_INFO *to_cs= thd->variables.collation_connection; + CHARSET_INFO *from_cs; + const char *buf; + uint buf_len; + bool need_conversion; + //// psergey: find the variable and convert it. + LINT_INIT(from_cs); + user_var_entry *entry; + uint32 unused; + if ((entry= + (user_var_entry*)hash_search(&thd->user_vars, + (byte*)lex->prepared_stmt_code.str, + lex->prepared_stmt_code.length)) + && entry->value) + { + switch (entry->type) + { + case REAL_RESULT: + str.set(*(double*)entry->value, NOT_FIXED_DEC, to_cs); + buf_len= str.length(); + buf= str.ptr(); + need_conversion= false; + break; + case INT_RESULT: + str.set(*(longlong*)entry->value, to_cs); + buf_len= str.length(); + buf= str.ptr(); + need_conversion= false; + break; + case STRING_RESULT: + buf_len= entry->length; + buf= entry->value; + from_cs = entry->collation.collation; + need_conversion= String::needs_conversion(entry->length, from_cs, + to_cs, &unused); + break; + default: + buf= ""; + need_conversion= false; + buf_len= 0; + DBUG_ASSERT(0); + } + } + else + { + from_cs= &my_charset_bin; + str.set("NULL", 4, from_cs); + buf= str.ptr(); + buf_len= str.length(); + need_conversion= String::needs_conversion(str.length(), from_cs, + to_cs, &unused); + } + + query_len = need_conversion? (buf_len* to_cs->mbmaxlen) : buf_len; + if (!(query_str= alloc_root(&thd->mem_root, query_len+1))) + { + send_error(thd, ER_OUT_OF_RESOURCES); + } + + if (need_conversion) + query_len= copy_and_convert(query_str, query_len, to_cs, buf, buf_len, + from_cs); + else + memcpy(query_str, buf, query_len); + query_str[query_len] = 0; + } + else + { + DBUG_PRINT("info", ("PREPARE: %.*s FROM '%.*s' \n", + lex->prepared_stmt_name.length, + lex->prepared_stmt_name.str, + lex->prepared_stmt_code.length, + lex->prepared_stmt_code.str)); + query_str= lex->prepared_stmt_code.str; + query_len= lex->prepared_stmt_code.length + 1; + } thd->command= COM_PREPARE; - if (!mysql_stmt_prepare(thd, lex->prepared_stmt_code.str, - lex->prepared_stmt_code.length + 1, + if (!mysql_stmt_prepare(thd, query_str, query_len + 1, &lex->prepared_stmt_name)) send_ok(thd, 0L, 0L, "Statement prepared"); break; @@ -2005,7 +2081,12 @@ mysql_execute_command(THD *thd) send_ok(thd); } else - send_error(thd,ER_UNKNOWN_STMT_HANDLER,"Undefined prepared statement"); + { + res= -1; + my_error(ER_UNKNOWN_STMT_HANDLER, MYF(0), + lex->prepared_stmt_name.length, lex->prepared_stmt_name.str, + "DEALLOCATE PREPARE"); + } break; } case SQLCOM_DO: @@ -3438,7 +3519,7 @@ error: */ int check_one_table_access(THD *thd, ulong privilege, TABLE_LIST *tables) - + { if (check_access(thd, privilege, tables->db, &tables->grant.privilege,0,0)) return 1; diff --git a/sql/sql_prepare.cc b/sql/sql_prepare.cc index 1612432cd67..af489d20783 100644 --- a/sql/sql_prepare.cc +++ b/sql/sql_prepare.cc @@ -135,7 +135,8 @@ find_prepared_statement(THD *thd, ulong id, const char *where, if (stmt == 0 || stmt->type() != Statement::PREPARED_STATEMENT) { - my_error(ER_UNKNOWN_STMT_HANDLER, MYF(0), id, where); + char llbuf[22]; + my_error(ER_UNKNOWN_STMT_HANDLER, MYF(0), 22, llstr(id, llbuf), where); if (se == SEND_ERROR) send_error(thd); return 0; @@ -392,7 +393,7 @@ void set_param_date(Item_param *param, uchar **pos, ulong len) void set_param_str(Item_param *param, uchar **pos, ulong len) { ulong length= get_param_length(pos, len); - param->set_value((const char *)*pos, length); + param->set_value((const char *)*pos, length, Item::default_charset()); *pos+= length; } @@ -1376,7 +1377,7 @@ static int send_prepare_results(Prepared_statement *stmt, bool text_protocol) goto error; } if (res == 0) - DBUG_RETURN(text_protocol?0:send_prep_stmt(stmt, 0)); + DBUG_RETURN(text_protocol? 0 : send_prep_stmt(stmt, 0)); error: if (res < 0) send_error(thd, thd->killed ? ER_SERVER_SHUTDOWN : 0); @@ -1417,25 +1418,31 @@ static bool init_param_array(Prepared_statement *stmt) /* + Given a query string with parameter markers, create a Prepared Statement + from it and send PS info back to the client. + SYNOPSIS mysql_stmt_prepare() - packet Prepared query - packet_length query length, with ignored trailing NULL or quote char. + packet query to be prepared + packet_length query string length, including ignored trailing NULL or + quote char. name NULL or statement name. For unnamed statements binary PS - protocol is used, for named statmenents text protocol is + protocol is used, for named statements text protocol is used. + RETURN + 0 OK, statement prepared successfully + other Error + + NOTES + This function parses the query and sends the total number of parameters + and resultset metadata information back to client (if any), without + executing the query i.e. without any log/disk writes. This allows the + queries to be re-executed without re-parsing during execute. - Parse the query and send the total number of parameters - and resultset metadata information back to client (if any), - without executing the query i.e. without any log/disk - writes. This will allow the queries to be re-executed - without re-parsing during execute. - - If parameter markers are found in the query, then store - the information using Item_param along with maintaining a - list in lex->param_array, so that a fast and direct - retrieval can be made without going through all field - items. + If parameter markers are found in the query, then store the information + using Item_param along with maintaining a list in lex->param_array, so + that a fast and direct retrieval can be made without going through all + field items. */ @@ -1672,13 +1679,14 @@ set_params_data_err: void mysql_sql_stmt_execute(THD *thd, LEX_STRING *stmt_name) { Prepared_statement *stmt; - DBUG_ENTER("mysql_stmt_execute"); + DBUG_ENTER("mysql_sql_stmt_execute"); if (!(stmt= (Prepared_statement*)thd->stmt_map.find_by_name(stmt_name))) { - send_error(thd, ER_UNKNOWN_STMT_HANDLER, - "Undefined prepared statement"); - DBUG_VOID_RETURN; + my_error(ER_UNKNOWN_STMT_HANDLER, MYF(0), stmt_name->length, + stmt_name->str, "EXECUTE"); + send_error(thd); + DBUG_VOID_RETURN; } if (stmt->param_count != thd->lex->prepared_stmt_params.elements) diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy index 6a683a26b01..bb226f76b2c 100644 --- a/sql/sql_yacc.yy +++ b/sql/sql_yacc.yy @@ -726,7 +726,7 @@ bool my_yyoverflow(short **a, YYSTYPE **b,int *yystacksize); precision subselect_start opt_and charset subselect_end select_var_list select_var_list_init help opt_len opt_extended_describe - prepare execute deallocate + prepare prepare_src execute deallocate END_OF_INPUT %type @@ -816,7 +816,7 @@ deallocate: }; prepare: - PREPARE_SYM ident FROM TEXT_STRING_sys + PREPARE_SYM ident FROM prepare_src { THD *thd=YYTHD; LEX *lex= thd->lex; @@ -827,9 +827,24 @@ prepare: } lex->sql_command= SQLCOM_PREPARE; lex->prepared_stmt_name= $2; - lex->prepared_stmt_code= $4; }; +prepare_src: + TEXT_STRING_sys + { + THD *thd=YYTHD; + LEX *lex= thd->lex; + lex->prepared_stmt_code= $1; + lex->prepared_stmt_code_is_varref= false; + } + | '@' ident_or_text + { + THD *thd=YYTHD; + LEX *lex= thd->lex; + lex->prepared_stmt_code= $2; + lex->prepared_stmt_code_is_varref= true; + }; + execute: EXECUTE_SYM ident { From 700adb9ae99626b66a03229a07fe3ec70da370f7 Mon Sep 17 00:00:00 2001 From: "sergefp@mysql.com" <> Date: Mon, 24 May 2004 21:08:22 +0400 Subject: [PATCH 10/13] Added support for PREPARE stmt1 FROM @var, Fixed the problem of previous patch with replication, More post-review fixes --- sql/sql_parse.cc | 27 ++++++------ sql/sql_prepare.cc | 108 +++++++++++++++++++++++++++++---------------- 2 files changed, 83 insertions(+), 52 deletions(-) diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index 013cac12e9b..1d5012a97a0 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -1978,17 +1978,21 @@ mysql_execute_command(THD *thd) uint query_len; if (lex->prepared_stmt_code_is_varref) { - /* This is PREPARE stmt FROM @var*/ + /* This is PREPARE stmt FROM @var. */ String str; CHARSET_INFO *to_cs= thd->variables.collation_connection; CHARSET_INFO *from_cs; const char *buf; uint buf_len; bool need_conversion; - //// psergey: find the variable and convert it. - LINT_INIT(from_cs); + LINT_INIT(from_cs); /* protected by need_conversion */ user_var_entry *entry; uint32 unused; + /* + Convert @var contents to string in connection character set. Although + it is known that int/real/NULL value cannot be a valid query we still + convert it for error messages to uniform. + */ if ((entry= (user_var_entry*)hash_search(&thd->user_vars, (byte*)lex->prepared_stmt_code.str, @@ -2033,32 +2037,29 @@ mysql_execute_command(THD *thd) to_cs, &unused); } - query_len = need_conversion? (buf_len* to_cs->mbmaxlen) : buf_len; + query_len = need_conversion? (buf_len * to_cs->mbmaxlen) : buf_len; if (!(query_str= alloc_root(&thd->mem_root, query_len+1))) - { send_error(thd, ER_OUT_OF_RESOURCES); - } if (need_conversion) query_len= copy_and_convert(query_str, query_len, to_cs, buf, buf_len, from_cs); else memcpy(query_str, buf, query_len); - query_str[query_len] = 0; + query_str[query_len]= 0; } else { + query_str= lex->prepared_stmt_code.str; + query_len= lex->prepared_stmt_code.length; DBUG_PRINT("info", ("PREPARE: %.*s FROM '%.*s' \n", lex->prepared_stmt_name.length, lex->prepared_stmt_name.str, - lex->prepared_stmt_code.length, - lex->prepared_stmt_code.str)); - query_str= lex->prepared_stmt_code.str; - query_len= lex->prepared_stmt_code.length + 1; + query_len, query_str)); } thd->command= COM_PREPARE; - if (!mysql_stmt_prepare(thd, query_str, query_len + 1, - &lex->prepared_stmt_name)) + if (!mysql_stmt_prepare(thd, query_str, query_len + 1, + &lex->prepared_stmt_name)) send_ok(thd, 0L, 0L, "Statement prepared"); break; } diff --git a/sql/sql_prepare.cc b/sql/sql_prepare.cc index 39add455b45..32e70f24c9f 100644 --- a/sql/sql_prepare.cc +++ b/sql/sql_prepare.cc @@ -94,19 +94,21 @@ public: bool log_full_query; #ifndef EMBEDDED_LIBRARY bool (*set_params)(Prepared_statement *st, uchar *data, uchar *data_end, - uchar *read_pos); + uchar *read_pos, String *expanded_query); #else - bool (*set_params_data)(Prepared_statement *st); + bool (*set_params_data)(Prepared_statement *st, String *expanded_query); #endif bool (*set_params_from_vars)(Prepared_statement *stmt, - List& varnames); + List& varnames, + String *expanded_query); public: Prepared_statement(THD *thd_arg); virtual ~Prepared_statement(); virtual Statement::Type type() const; }; -static void execute_stmt(THD *thd, Prepared_statement *stmt); +static void execute_stmt(THD *thd, Prepared_statement *stmt, + String *expanded_query); /****************************************************************************** Implementation @@ -517,19 +519,20 @@ static void setup_one_conversion_function(Item_param *param, uchar param_type) */ static bool insert_params_withlog(Prepared_statement *stmt, uchar *null_array, - uchar *read_pos, uchar *data_end) + uchar *read_pos, uchar *data_end, + String *query) { THD *thd= stmt->thd; Item_param **begin= stmt->param_array; Item_param **end= begin + stmt->param_count; uint32 length= 0; - String str, query; + String str; const String *res; DBUG_ENTER("insert_params_withlog"); - if (query.copy(stmt->query, stmt->query_length, default_charset_info)) + if (query->copy(stmt->query, stmt->query_length, default_charset_info)) DBUG_RETURN(1); for (Item_param **it= begin; it < end; ++it) @@ -552,20 +555,18 @@ static bool insert_params_withlog(Prepared_statement *stmt, uchar *null_array, res= param->query_val_str(&str); } } - if (query.replace(param->pos_in_query+length, 1, *res)) + if (query->replace(param->pos_in_query+length, 1, *res)) DBUG_RETURN(1); length+= res->length()-1; } - if (alloc_query(thd, (char *)query.ptr(), query.length()+1)) - DBUG_RETURN(1); - DBUG_RETURN(0); } static bool insert_params(Prepared_statement *stmt, uchar *null_array, - uchar *read_pos, uchar *data_end) + uchar *read_pos, uchar *data_end, + String *expanded_query) { Item_param **begin= stmt->param_array; Item_param **end= begin + stmt->param_count; @@ -627,7 +628,7 @@ static bool setup_conversion_functions(Prepared_statement *stmt, #else -static bool emb_insert_params(Prepared_statement *stmt) +static bool emb_insert_params(Prepared_statement *stmt, String *expanded_query) { Item_param **it= stmt->param_array; Item_param **end= it + stmt->param_count; @@ -658,20 +659,20 @@ static bool emb_insert_params(Prepared_statement *stmt) } -static bool emb_insert_params_withlog(Prepared_statement *stmt) +static bool emb_insert_params_withlog(Prepared_statement *stmt, String *query) { THD *thd= stmt->thd; Item_param **it= stmt->param_array; Item_param **end= it + stmt->param_count; MYSQL_BIND *client_param= thd->client_params; - String str, query; + String str; const String *res; uint32 length= 0; DBUG_ENTER("emb_insert_params_withlog"); - if (query.copy(stmt->query, stmt->query_length, default_charset_info)) + if (query->copy(stmt->query, stmt->query_length, default_charset_info)) DBUG_RETURN(1); for (; it < end; ++it, ++client_param) @@ -697,14 +698,10 @@ static bool emb_insert_params_withlog(Prepared_statement *stmt) res= param->query_val_str(&str); } } - if (query.replace(param->pos_in_query+length, 1, *res)) + if (query->replace(param->pos_in_query+length, 1, *res)) DBUG_RETURN(1); length+= res->length()-1; } - - if (alloc_query(thd, (char *) query.ptr(), query.length()+1)) - DBUG_RETURN(1); - DBUG_RETURN(0); } @@ -718,11 +715,12 @@ static bool emb_insert_params_withlog(Prepared_statement *stmt) stmt Statement varnames List of variables. Caller must ensure that number of variables in the list is equal to number of statement parameters - + query Ignored */ static bool insert_params_from_vars(Prepared_statement *stmt, - List& varnames) + List& varnames, + String *query __attribute__((unused))) { Item_param **begin= stmt->param_array; Item_param **end= begin + stmt->param_count; @@ -763,8 +761,21 @@ static bool insert_params_from_vars(Prepared_statement *stmt, DBUG_RETURN(0); } + +/* + Do the same as insert_params_from_vars but also construct query text for + binary log. + SYNOPSIS + insert_params_from_vars() + stmt Statement + varnames List of variables. Caller must ensure that number of variables + in the list is equal to number of statement parameters + query The query with parameter markers replaced with their values +*/ + static bool insert_params_from_vars_with_log(Prepared_statement *stmt, - List& varnames) + List& varnames, + String *query) { Item_param **begin= stmt->param_array; Item_param **end= begin + stmt->param_count; @@ -773,10 +784,10 @@ static bool insert_params_from_vars_with_log(Prepared_statement *stmt, DBUG_ENTER("insert_params_from_vars"); List_iterator var_it(varnames); - String str, query; + String str; const String *res; uint32 length= 0; - if (query.copy(stmt->query, stmt->query_length, default_charset_info)) + if (query->copy(stmt->query, stmt->query_length, default_charset_info)) DBUG_RETURN(1); for (Item_param **it= begin; it < end; ++it) @@ -812,12 +823,10 @@ static bool insert_params_from_vars_with_log(Prepared_statement *stmt, res= &my_null_string; } - if (query.replace(param->pos_in_query+length, 1, *res)) + if (query->replace(param->pos_in_query+length, 1, *res)) DBUG_RETURN(1); length+= res->length()-1; } - if (alloc_query(stmt->thd, (char *) query.ptr(), query.length()+1)) - DBUG_RETURN(1); DBUG_RETURN(0); } @@ -1708,13 +1717,14 @@ void mysql_stmt_execute(THD *thd, char *packet, uint packet_length) DBUG_VOID_RETURN; } - + String expanded_query; #ifndef EMBEDDED_LIBRARY if (stmt->param_count) { uchar *null_array= (uchar *) packet; if (setup_conversion_functions(stmt, (uchar **) &packet, packet_end) || - stmt->set_params(stmt, null_array, (uchar *) packet, packet_end)) + stmt->set_params(stmt, null_array, (uchar *) packet, packet_end, + &expanded_query)) goto set_params_data_err; } #else @@ -1727,7 +1737,7 @@ void mysql_stmt_execute(THD *thd, char *packet, uint packet_length) goto set_params_data_err; #endif thd->protocol= &thd->protocol_prep; // Switch to binary protocol - execute_stmt(thd, stmt); + execute_stmt(thd, stmt, &expanded_query); thd->protocol= &thd->protocol_simple; // Use normal protocol DBUG_VOID_RETURN; @@ -1747,6 +1757,7 @@ set_params_data_err: void mysql_sql_stmt_execute(THD *thd, LEX_STRING *stmt_name) { Prepared_statement *stmt; + String expanded_query; DBUG_ENTER("mysql_sql_stmt_execute"); if (!(stmt= (Prepared_statement*)thd->stmt_map.find_by_name(stmt_name))) @@ -1766,27 +1777,47 @@ void mysql_sql_stmt_execute(THD *thd, LEX_STRING *stmt_name) /* Item_param allows setting parameters in COM_EXECUTE only */ thd->command= COM_EXECUTE; - if (stmt->set_params_from_vars(stmt, thd->lex->prepared_stmt_params)) + if (stmt->set_params_from_vars(stmt, thd->lex->prepared_stmt_params, + &expanded_query)) { my_error(ER_WRONG_ARGUMENTS, MYF(0), "mysql_execute"); send_error(thd); } - execute_stmt(thd, stmt); + execute_stmt(thd, stmt, &expanded_query); DBUG_VOID_RETURN; } + /* Execute prepared statement. + SYNOPSIS + execute_stmt() + thd Current thread + stmt Statement to execute + expanded_query If binary log is enabled, query string with parameter + placeholders replaced with actual values. Otherwise empty + string. + NOTES Caller must set parameter values and thd::protocol. thd->free_list is assumed to be garbage. */ -static void execute_stmt(THD *thd, Prepared_statement *stmt) + +static void execute_stmt(THD *thd, Prepared_statement *stmt, + String *expanded_query) { DBUG_ENTER("execute_stmt"); thd->free_list= NULL; thd->stmt_backup.set_statement(thd); thd->set_statement(stmt); reset_stmt_for_execute(stmt); + + if (expanded_query->length() && + alloc_query(thd, (char *)expanded_query->ptr(), + expanded_query->length()+1)) + { + my_error(ER_OUTOFMEMORY, 0, expanded_query->length()); + DBUG_VOID_RETURN; + } if (!(specialflag & SPECIAL_NO_PRIOR)) my_pthread_setprio(pthread_self(),QUERY_PRIOR); @@ -1808,13 +1839,12 @@ static void execute_stmt(THD *thd, Prepared_statement *stmt) } - /* - Reset a prepared statement in case there was a recoverable error. + Reset a prepared statement in case there was a recoverable error. SYNOPSIS mysql_stmt_reset() - thd Thread handle - packet Packet with stmt id + thd Thread handle + packet Packet with stmt id DESCRIPTION This function resets statement to the state it was right after prepare. From b142c8edd083b5c3fa37f1b9cef411bc190c0904 Mon Sep 17 00:00:00 2001 From: "sergefp@mysql.com" <> Date: Mon, 24 May 2004 21:12:05 +0400 Subject: [PATCH 11/13] Added replication tests --- mysql-test/r/rpl_ps.result | 28 +++++++++++++++++++++++++ mysql-test/t/rpl_ps.test | 43 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 71 insertions(+) create mode 100644 mysql-test/r/rpl_ps.result create mode 100644 mysql-test/t/rpl_ps.test diff --git a/mysql-test/r/rpl_ps.result b/mysql-test/r/rpl_ps.result new file mode 100644 index 00000000000..c969575de76 --- /dev/null +++ b/mysql-test/r/rpl_ps.result @@ -0,0 +1,28 @@ +stop slave; +drop table if exists t1,t2,t3,t4,t5,t6,t7,t8,t9; +reset master; +reset slave; +drop table if exists t1,t2,t3,t4,t5,t6,t7,t8,t9; +start slave; +drop table if exists t1; +create table t1(n char(30)); +prepare stmt1 from 'insert into t1 values (?)'; +set @var1= "from-master-1"; +execute stmt1 using @var1; +set @var1= "from-master-2-'',"; +execute stmt1 using @var1; +select * from t1; +n +from-master-1 +from-master-2-'', +set @var2= 'insert into t1 values (concat("from-var-", ?))'; +prepare stmt2 from @var2; +set @var1='from-master-3'; +execute stmt2 using @var1; +select * from t1; +n +from-master-1 +from-master-2-'', +from-var-from-master-3 +drop table t1; +stop slave; diff --git a/mysql-test/t/rpl_ps.test b/mysql-test/t/rpl_ps.test new file mode 100644 index 00000000000..79f48381a4f --- /dev/null +++ b/mysql-test/t/rpl_ps.test @@ -0,0 +1,43 @@ +# +# Test of replicating user variables +# +source include/master-slave.inc; + +#save_master_pos; +#connection slave; +#sync_with_master; +#reset master; +#connection master; + +--disable_warnings +drop table if exists t1; +--enable_warnings + +create table t1(n char(30)); + +prepare stmt1 from 'insert into t1 values (?)'; +set @var1= "from-master-1"; +execute stmt1 using @var1; +set @var1= "from-master-2-'',"; +execute stmt1 using @var1; +select * from t1; + +set @var2= 'insert into t1 values (concat("from-var-", ?))'; +prepare stmt2 from @var2; +set @var1='from-master-3'; +execute stmt2 using @var1; + +save_master_pos; +connection slave; +sync_with_master; +select * from t1; + +connection master; + +drop table t1; + +save_master_pos; +connection slave; +sync_with_master; +stop slave; + From e3b03d7a08daef390a1ef62124dc721745b44e90 Mon Sep 17 00:00:00 2001 From: "sergefp@mysql.com" <> Date: Tue, 1 Jun 2004 17:27:40 +0400 Subject: [PATCH 12/13] * New, binlog-aware character sets support in SQL Syntax for Prepared statements. * The prepared statement query is put into binary log on execution only if it is an update query. --- sql/item_func.cc | 78 ++++++++++++++++------ sql/mysql_priv.h | 3 + sql/sql_prepare.cc | 158 ++++++++++++++++++++++++++++++++++++--------- 3 files changed, 190 insertions(+), 49 deletions(-) diff --git a/sql/item_func.cc b/sql/item_func.cc index f221e0dcc5c..2fc1f68b49c 100644 --- a/sql/item_func.cc +++ b/sql/item_func.cc @@ -2583,27 +2583,39 @@ longlong Item_func_get_user_var::val_int() /* + Get variable by name and, if necessary, put the record of variable + use into the binary log. + + SYNOPSIS + get_var_with_binlog() + thd Current thread + name Variable name + out_entry [out] variable structure or NULL. The pointer is set + regardless of whether function succeeded or not. + When a user variable is invoked from an update query (INSERT, UPDATE etc), stores this variable and its value in thd->user_var_events, so that it can be written to the binlog (will be written just before the query is written, see log.cc). + + RETURN + 0 OK + 1 Failed to put appropiate record into binary log + */ -void Item_func_get_user_var::fix_length_and_dec() +int get_var_with_binlog(THD *thd, LEX_STRING &name, + user_var_entry **out_entry) { - THD *thd=current_thd; BINLOG_USER_VAR_EVENT *user_var_event; - maybe_null=1; - decimals=NOT_FIXED_DEC; - max_length=MAX_BLOB_WIDTH; - - if (!(var_entry= get_variable(&thd->user_vars, name, 0))) - null_value= 1; - else - collation.set(var_entry->collation); + user_var_entry *var_entry; + var_entry= get_variable(&thd->user_vars, name, 0); if (!(opt_bin_log && is_update_query(thd->lex->sql_command))) - return; + { + *out_entry= var_entry; + return 0; + } if (!var_entry) { @@ -2630,13 +2642,16 @@ void Item_func_get_user_var::fix_length_and_dec() if (!(var_entry= get_variable(&thd->user_vars, name, 0))) goto err; } - /* - If this variable was already stored in user_var_events by this query - (because it's used in more than one place in the query), don't store - it. - */ else if (var_entry->used_query_id == thd->query_id) - return; + { + /* + If this variable was already stored in user_var_events by this query + (because it's used in more than one place in the query), don't store + it. + */ + *out_entry= var_entry; + return 0; + } uint size; /* @@ -2671,11 +2686,34 @@ void Item_func_get_user_var::fix_length_and_dec() var_entry->used_query_id= thd->query_id; if (insert_dynamic(&thd->user_var_events, (gptr) &user_var_event)) goto err; - - return; + + *out_entry= var_entry; + return 0; err: - thd->fatal_error(); + *out_entry= var_entry; + return 1; +} + + +void Item_func_get_user_var::fix_length_and_dec() +{ + THD *thd=current_thd; + int error; + maybe_null=1; + decimals=NOT_FIXED_DEC; + max_length=MAX_BLOB_WIDTH; + + error= get_var_with_binlog(thd, name, &var_entry); + + if (!var_entry) + null_value= 1; + else + collation.set(var_entry->collation); + + if (error) + thd->fatal_error(); + return; } diff --git a/sql/mysql_priv.h b/sql/mysql_priv.h index fe3efd720f0..2a88f6843fc 100644 --- a/sql/mysql_priv.h +++ b/sql/mysql_priv.h @@ -1069,6 +1069,9 @@ Item *get_system_var(THD *thd, enum_var_type var_type, LEX_STRING name, LEX_STRING component); Item *get_system_var(THD *thd, enum_var_type var_type, const char *var_name, uint length, const char *item_name); +/* item_func.cc */ +int get_var_with_binlog(THD *thd, LEX_STRING &name, + user_var_entry **out_entry); /* log.cc */ bool flush_error_log(void); diff --git a/sql/sql_prepare.cc b/sql/sql_prepare.cc index 1224d1da194..70b6a9de006 100644 --- a/sql/sql_prepare.cc +++ b/sql/sql_prepare.cc @@ -101,11 +101,12 @@ public: public: Prepared_statement(THD *thd_arg); virtual ~Prepared_statement(); + void setup_set_params(); virtual Statement::Type type() const; }; static void execute_stmt(THD *thd, Prepared_statement *stmt, - String *expanded_query); + String *expanded_query, bool set_context=false); /****************************************************************************** Implementation @@ -769,12 +770,14 @@ static bool emb_insert_params_withlog(Prepared_statement *stmt, String *query) *client_param->length : client_param->buffer_length); } - res= param->query_val_str(&str); - if (param->convert_str_value(thd)) - DBUG_RETURN(1); /* out of memory */ } + res= param->query_val_str(&str); + if (param->convert_str_value(thd)) + DBUG_RETURN(1); /* out of memory */ + if (query->replace(param->pos_in_query+length, 1, *res)) DBUG_RETURN(1); + length+= res->length()-1; } DBUG_RETURN(0); @@ -820,18 +823,45 @@ static bool insert_params_from_vars(Prepared_statement *stmt, param->set_double(*(double*)entry->value); break; case INT_RESULT: - param->set_int(*(longlong*)entry->value); + param->set_int(*(longlong*)entry->value, 21); break; case STRING_RESULT: - param->set_value(entry->value, entry->length, - entry->collation.collation); + { + CHARSET_INFO *fromcs= entry->collation.collation; + CHARSET_INFO *tocs= stmt->thd->variables.collation_connection; + uint32 dummy_offset; + + param->value.cs_info.character_set_client= fromcs; + + /* + Setup source and destination character sets so that they + are different only if conversion is necessary: this will + make later checks easier. + */ + param->value.cs_info.final_character_set_of_str_value= + String::needs_conversion(0, fromcs, tocs, &dummy_offset) ? + tocs : fromcs; + /* + Exact value of max_length is not known unless data is converted to + charset of connection, so we have to set it later. + */ + param->item_type= Item::STRING_ITEM; + param->item_result_type= STRING_RESULT; + + if (param->set_str((const char *)entry->value, entry->length)) + DBUG_RETURN(1); + } break; default: DBUG_ASSERT(0); + param->set_null(); } } else - param->maybe_null= param->null_value= param->value_is_set= 1; + param->set_null(); + + if (param->convert_str_value(stmt->thd)) + DBUG_RETURN(1); /* out of memory */ } DBUG_RETURN(0); } @@ -869,10 +899,10 @@ static bool insert_params_from_vars_with_log(Prepared_statement *stmt, { Item_param *param= *it; varname= var_it++; - if ((entry= (user_var_entry*)hash_search(&stmt->thd->user_vars, - (byte*) varname->str, - varname->length)) - && entry->value) + if (get_var_with_binlog(stmt->thd, *varname, &entry)) + DBUG_RETURN(1); + DBUG_ASSERT(entry); + if (entry->value) { param->item_result_type= entry->type; switch (entry->type) @@ -881,26 +911,65 @@ static bool insert_params_from_vars_with_log(Prepared_statement *stmt, param->set_double(*(double*)entry->value); break; case INT_RESULT: - param->set_int(*(longlong*)entry->value); + param->set_int(*(longlong*)entry->value, 21); break; case STRING_RESULT: - param->set_value(entry->value, entry->length, - entry->collation.collation); + { + CHARSET_INFO *fromcs= entry->collation.collation; + CHARSET_INFO *tocs= stmt->thd->variables.collation_connection; + uint32 dummy_offset; + + param->value.cs_info.character_set_client= fromcs; + + /* + Setup source and destination character sets so that they + are different only if conversion is necessary: this will + make later checks easier. + */ + param->value.cs_info.final_character_set_of_str_value= + String::needs_conversion(0, fromcs, tocs, &dummy_offset) ? + tocs : fromcs; + /* + Exact value of max_length is not known unless data is converted to + charset of connection, so we have to set it later. + */ + param->item_type= Item::STRING_ITEM; + param->item_result_type= STRING_RESULT; + + if (param->set_str((const char *)entry->value, entry->length)) + DBUG_RETURN(1); + } break; default: DBUG_ASSERT(0); + param->set_null(); } - res= param->query_val_str(&str); } else - { - param->maybe_null= param->null_value= param->value_is_set= 1; - res= &my_null_string; - } + param->set_null(); - if (query->replace(param->pos_in_query+length, 1, *res)) + /* Insert @'escaped-varname' instead of parameter in the query */ + char *buf, *ptr; + str.length(0); + if (str.reserve(entry->name.length*2+3)) DBUG_RETURN(1); - length+= res->length()-1; + + buf= str.c_ptr_quick(); + ptr= buf; + *ptr++= '@'; + *ptr++= '\''; + ptr+= + escape_string_for_mysql(&my_charset_utf8_general_ci, + ptr, entry->name.str, entry->name.length); + *ptr++= '\''; + str.length(ptr - buf); + + if (param->convert_str_value(stmt->thd)) + DBUG_RETURN(1); /* out of memory */ + + if (query->replace(param->pos_in_query+length, 1, str)) + DBUG_RETURN(1); + length+= str.length()-1; } DBUG_RETURN(0); } @@ -1680,6 +1749,7 @@ int mysql_stmt_prepare(THD *thd, char *packet, uint packet_length, } else { + stmt->setup_set_params(); SELECT_LEX *sl= stmt->lex->all_selects_list; /* Save WHERE clause pointers, because they may be changed during query @@ -1689,7 +1759,9 @@ int mysql_stmt_prepare(THD *thd, char *packet, uint packet_length, { sl->prep_where= sl->where; } + } + DBUG_RETURN(!stmt); } @@ -1809,7 +1881,7 @@ void mysql_stmt_execute(THD *thd, char *packet, uint packet_length) we set params, and also we don't need to parse packet. So we do it in one function. */ - if (stmt->param_count && stmt->set_params_data(stmt)) + if (stmt->param_count && stmt->set_params_data(stmt, &expanded_query)) goto set_params_data_err; #endif thd->protocol= &thd->protocol_prep; // Switch to binary protocol @@ -1853,7 +1925,10 @@ void mysql_sql_stmt_execute(THD *thd, LEX_STRING *stmt_name) /* Item_param allows setting parameters in COM_EXECUTE only */ thd->command= COM_EXECUTE; - if (stmt->set_params_from_vars(stmt, thd->lex->prepared_stmt_params, + thd->free_list= NULL; + thd->stmt_backup.set_statement(thd); + thd->set_statement(stmt); + if (stmt->set_params_from_vars(stmt, thd->stmt_backup.lex->prepared_stmt_params, &expanded_query)) { my_error(ER_WRONG_ARGUMENTS, MYF(0), "mysql_execute"); @@ -1879,12 +1954,15 @@ void mysql_sql_stmt_execute(THD *thd, LEX_STRING *stmt_name) */ static void execute_stmt(THD *thd, Prepared_statement *stmt, - String *expanded_query) + String *expanded_query, bool set_context) { DBUG_ENTER("execute_stmt"); - thd->free_list= NULL; - thd->stmt_backup.set_statement(thd); - thd->set_statement(stmt); + if (set_context) + { + thd->free_list= NULL; + thd->stmt_backup.set_statement(thd); + thd->set_statement(stmt); + } reset_stmt_for_execute(stmt); if (expanded_query->length() && @@ -2060,7 +2138,7 @@ Prepared_statement::Prepared_statement(THD *thd_arg) get_longdata_error(0) { *last_error= '\0'; - if (mysql_bin_log.is_open()) + if (mysql_bin_log.is_open()) //psergey-todo: remove this! { set_params_from_vars= insert_params_from_vars_with_log; #ifndef EMBEDDED_LIBRARY @@ -2080,6 +2158,28 @@ Prepared_statement::Prepared_statement(THD *thd_arg) } } +void Prepared_statement::setup_set_params() +{ + /* Setup binary logging */ + if (mysql_bin_log.is_open() && is_update_query(lex->sql_command)) + { + set_params_from_vars= insert_params_from_vars_with_log; +#ifndef EMBEDDED_LIBRARY + set_params= insert_params_withlog; +#else + set_params_data= emb_insert_params_withlog; +#endif + } + else + { + set_params_from_vars= insert_params_from_vars; +#ifndef EMBEDDED_LIBRARY + set_params= insert_params; +#else + set_params_data= emb_insert_params; +#endif + } +} Prepared_statement::~Prepared_statement() { From 1e05e6cb82a28e18f80ae807d16751eedafaa074 Mon Sep 17 00:00:00 2001 From: "sergefp@mysql.com" <> Date: Mon, 7 Jun 2004 12:09:10 +0400 Subject: [PATCH 13/13] Post review fixes for "SQL Syntax for Prepared Statements". --- mysql-test/r/ps.result | 2 +- mysys/my_error.c | 45 ++++----- sql/item.cc | 70 +++++++++++++- sql/item.h | 1 + sql/item_func.cc | 14 +-- sql/mysql_priv.h | 1 + sql/sql_class.cc | 2 +- sql/sql_parse.cc | 80 ++++++---------- sql/sql_prepare.cc | 201 +++++++++++------------------------------ sql/sql_yacc.yy | 10 +- 10 files changed, 182 insertions(+), 244 deletions(-) diff --git a/mysql-test/r/ps.result b/mysql-test/r/ps.result index 6c228327b8d..ccf855a927b 100644 --- a/mysql-test/r/ps.result +++ b/mysql-test/r/ps.result @@ -23,7 +23,7 @@ a b deallocate prepare no_such_statement; ERROR HY000: Unknown prepared statement handler (no_such_statement) given to DEALLOCATE PREPARE execute stmt1; -ERROR HY000: Wrong arguments to mysql_execute +ERROR HY000: Wrong arguments to EXECUTE prepare stmt2 from 'prepare nested_stmt from "select 1"'; 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 '"select 1"' at line 1 prepare stmt2 from 'execute stmt1'; diff --git a/mysys/my_error.c b/mysys/my_error.c index b16c39085fd..8a377f63c7e 100644 --- a/mysys/my_error.c +++ b/mysys/my_error.c @@ -37,8 +37,8 @@ char NEAR errbuff[NRERRBUFFS][ERRMSGSIZE]; The following subset of printf format is supported: "%[0-9.-]*l?[sdu]", where all length flags are parsed but ignored. - Additionally "%.*s" is supported and "%.*[ud]" is correctly parsed but - length value is ignored. + Additionally "%.*s" is supported and "%.*[ud]" is correctly parsed but + the length value is ignored. */ int my_error(int nr,myf MyFlags, ...) @@ -49,7 +49,7 @@ int my_error(int nr,myf MyFlags, ...) reg2 char *endpos; char * par; char ebuff[ERRMSGSIZE+20]; - int prec_chars; + int prec_chars; /* output precision */ my_bool prec_supplied; DBUG_ENTER("my_error"); LINT_INIT(prec_chars); /* protected by prec_supplied */ @@ -76,10 +76,11 @@ int my_error(int nr,myf MyFlags, ...) } else { - /* - Skip size/precision flags to be compatible with printf. - The only size/precision flag supported is "%.*s". - "%.*u" and "%.*d" cause + /* + Skip size/precision flags to be compatible with printf. + The only size/precision flag supported is "%.*s". + If "%.*u" or "%.*d" are encountered, the precision number is read + from the variable argument list but its value is ignored. */ prec_supplied= 0; if (*tpos== '.') @@ -94,52 +95,52 @@ int my_error(int nr,myf MyFlags, ...) prec_supplied= 1; } } - + if (!prec_supplied) { - while (my_isdigit(&my_charset_latin1, *tpos) || *tpos == '.' || + while (my_isdigit(&my_charset_latin1, *tpos) || *tpos == '.' || *tpos == '-') - tpos++; - - if (*tpos == 'l') /* Skipp 'l' argument */ + tpos++; + + if (*tpos == 'l') /* Skip 'l' argument */ tpos++; } if (*tpos == 's') /* String parameter */ { - par = va_arg(ap, char *); - plen = (uint) strlen(par); + par= va_arg(ap, char *); + plen= (uint) strlen(par); if (prec_supplied && prec_chars > 0) plen= min((uint)prec_chars, plen); if (olen + plen < ERRMSGSIZE+2) /* Replace if possible */ { - memcpy(endpos,par, plen); - endpos += plen; + strmake(endpos, par, plen); + endpos+= plen; tpos++; - olen+=plen-2; + olen+= plen-2; continue; } } else if (*tpos == 'd' || *tpos == 'u') /* Integer parameter */ { register int iarg; - iarg = va_arg(ap, int); + iarg= va_arg(ap, int); if (*tpos == 'd') plen= (uint) (int10_to_str((long) iarg, endpos, -10) - endpos); else plen= (uint) (int10_to_str((long) (uint) iarg, endpos, 10) - endpos); if (olen + plen < ERRMSGSIZE+2) /* Replace parameter if possible */ { - endpos+=plen; + endpos+= plen; tpos++; - olen+=plen-2; + olen+= plen-2; continue; } } } - *endpos++='%'; /* % used as % or unknown code */ + *endpos++= '%'; /* % used as % or unknown code */ } - *endpos='\0'; /* End of errmessage */ + *endpos= '\0'; /* End of errmessage */ va_end(ap); DBUG_RETURN((*error_handler_hook)(nr, ebuff, MyFlags)); } diff --git a/sql/item.cc b/sql/item.cc index ad209817d8a..cabae46ed71 100644 --- a/sql/item.cc +++ b/sql/item.cc @@ -255,7 +255,7 @@ bool Item::get_time(TIME *ltime) return 0; } -CHARSET_INFO * Item::default_charset() +CHARSET_INFO *Item::default_charset() { return current_thd->variables.collation_connection; } @@ -735,6 +735,70 @@ bool Item_param::set_longdata(const char *str, ulong length) } +/* + Set parameter value from user variable value. + + SYNOPSIS + set_from_user_var + thd Current thread + entry User variable structure (NULL means use NULL value) + + RETURN + 0 OK + 1 Out of memort +*/ + +bool Item_param::set_from_user_var(THD *thd, const user_var_entry *entry) +{ + DBUG_ENTER("Item_param::set_from_user_var"); + if (entry && entry->value) + { + item_result_type= entry->type; + switch (entry->type) + { + case REAL_RESULT: + set_double(*(double*)entry->value); + break; + case INT_RESULT: + set_int(*(longlong*)entry->value, 21); + break; + case STRING_RESULT: + { + CHARSET_INFO *fromcs= entry->collation.collation; + CHARSET_INFO *tocs= thd->variables.collation_connection; + uint32 dummy_offset; + + value.cs_info.character_set_client= fromcs; + /* + Setup source and destination character sets so that they + are different only if conversion is necessary: this will + make later checks easier. + */ + value.cs_info.final_character_set_of_str_value= + String::needs_conversion(0, fromcs, tocs, &dummy_offset) ? + tocs : fromcs; + /* + Exact value of max_length is not known unless data is converted to + charset of connection, so we have to set it later. + */ + item_type= Item::STRING_ITEM; + item_result_type= STRING_RESULT; + + if (set_str((const char *)entry->value, entry->length)) + DBUG_RETURN(1); + } + break; + default: + DBUG_ASSERT(0); + set_null(); + } + } + else + set_null(); + + DBUG_RETURN(0); +} + /* Resets parameter after execution. @@ -767,8 +831,6 @@ void Item_param::reset() int Item_param::save_in_field(Field *field, bool no_conversions) { - DBUG_ASSERT(current_thd->command == COM_EXECUTE); - field->set_notnull(); switch (state) { @@ -1666,7 +1728,7 @@ bool Item::send(Protocol *protocol, String *buffer) } case MYSQL_TYPE_TINY: { - longlong nr; + longlong nr; nr= val_int(); if (!null_value) result= protocol->store_tiny(nr); diff --git a/sql/item.h b/sql/item.h index 571eaccd33b..e1d35c5b286 100644 --- a/sql/item.h +++ b/sql/item.h @@ -489,6 +489,7 @@ public: bool set_str(const char *str, ulong length); bool set_longdata(const char *str, ulong length); void set_time(TIME *tm, timestamp_type type, uint32 max_length_arg); + bool set_from_user_var(THD *thd, const user_var_entry *entry); void reset(); /* Assign placeholder value from bind data. diff --git a/sql/item_func.cc b/sql/item_func.cc index 2fc1f68b49c..53c6884c5de 100644 --- a/sql/item_func.cc +++ b/sql/item_func.cc @@ -2703,17 +2703,17 @@ void Item_func_get_user_var::fix_length_and_dec() maybe_null=1; decimals=NOT_FIXED_DEC; max_length=MAX_BLOB_WIDTH; - + error= get_var_with_binlog(thd, name, &var_entry); - - if (!var_entry) - null_value= 1; - else + + if (var_entry) collation.set(var_entry->collation); - + else + null_value= 1; + if (error) thd->fatal_error(); - + return; } diff --git a/sql/mysql_priv.h b/sql/mysql_priv.h index 81dce036ccd..c9ac2038fa9 100644 --- a/sql/mysql_priv.h +++ b/sql/mysql_priv.h @@ -348,6 +348,7 @@ inline THD *_current_thd(void) #include "field.h" /* Field definitions */ #include "protocol.h" #include "sql_udf.h" +class user_var_entry; #include "item.h" typedef Comp_creator* (*chooser_compare_func_creator)(bool invert); /* sql_parse.cc */ diff --git a/sql/sql_class.cc b/sql/sql_class.cc index a1ca227afe8..704662fa4bf 100644 --- a/sql/sql_class.cc +++ b/sql/sql_class.cc @@ -233,7 +233,7 @@ THD::THD():user_time(0), current_statement(0), is_fatal_error(0), 16); else bzero((char*) &user_var_events, sizeof(user_var_events)); - + /* Protocol */ protocol= &protocol_simple; // Default protocol protocol_simple.init(this); diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index ac304554d78..384d05ad94e 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -1976,86 +1976,59 @@ mysql_execute_command(THD *thd) break; } case SQLCOM_PREPARE: - { + { char *query_str; uint query_len; if (lex->prepared_stmt_code_is_varref) { /* This is PREPARE stmt FROM @var. */ String str; + String *pstr; CHARSET_INFO *to_cs= thd->variables.collation_connection; - CHARSET_INFO *from_cs; - const char *buf; - uint buf_len; bool need_conversion; - LINT_INIT(from_cs); /* protected by need_conversion */ user_var_entry *entry; uint32 unused; /* - Convert @var contents to string in connection character set. Although - it is known that int/real/NULL value cannot be a valid query we still - convert it for error messages to uniform. + Convert @var contents to string in connection character set. Although + it is known that int/real/NULL value cannot be a valid query we still + convert it for error messages to uniform. */ - if ((entry= - (user_var_entry*)hash_search(&thd->user_vars, + if ((entry= + (user_var_entry*)hash_search(&thd->user_vars, (byte*)lex->prepared_stmt_code.str, lex->prepared_stmt_code.length)) && entry->value) { - switch (entry->type) - { - case REAL_RESULT: - str.set(*(double*)entry->value, NOT_FIXED_DEC, to_cs); - buf_len= str.length(); - buf= str.ptr(); - need_conversion= false; - break; - case INT_RESULT: - str.set(*(longlong*)entry->value, to_cs); - buf_len= str.length(); - buf= str.ptr(); - need_conversion= false; - break; - case STRING_RESULT: - buf_len= entry->length; - buf= entry->value; - from_cs = entry->collation.collation; - need_conversion= String::needs_conversion(entry->length, from_cs, - to_cs, &unused); - break; - default: - buf= ""; - need_conversion= false; - buf_len= 0; - DBUG_ASSERT(0); - } + String *pstr; + my_bool is_var_null; + pstr= entry->val_str(&is_var_null, &str, NOT_FIXED_DEC); + DBUG_ASSERT(!is_var_null); + if (!pstr) + send_error(thd, ER_OUT_OF_RESOURCES); + DBUG_ASSERT(pstr == &str); } else - { - from_cs= &my_charset_bin; - str.set("NULL", 4, from_cs); - buf= str.ptr(); - buf_len= str.length(); - need_conversion= String::needs_conversion(str.length(), from_cs, - to_cs, &unused); - } - - query_len = need_conversion? (buf_len * to_cs->mbmaxlen) : buf_len; + str.set("NULL", 4, &my_charset_latin1); + need_conversion= + String::needs_conversion(str.length(), str.charset(), to_cs, &unused); + + query_len= need_conversion? (str.length() * to_cs->mbmaxlen) : + str.length(); if (!(query_str= alloc_root(&thd->mem_root, query_len+1))) send_error(thd, ER_OUT_OF_RESOURCES); - + if (need_conversion) - query_len= copy_and_convert(query_str, query_len, to_cs, buf, buf_len, - from_cs); + query_len= copy_and_convert(query_str, query_len, to_cs, str.ptr(), + str.length(), str.charset()); else - memcpy(query_str, buf, query_len); + memcpy(query_str, str.ptr(), str.length()); query_str[query_len]= 0; } else { query_str= lex->prepared_stmt_code.str; query_len= lex->prepared_stmt_code.length; - DBUG_PRINT("info", ("PREPARE: %.*s FROM '%.*s' \n", + DBUG_PRINT("info", ("PREPARE: %.*s FROM '%.*s' \n", lex->prepared_stmt_name.length, lex->prepared_stmt_name.str, query_len, query_str)); @@ -2068,7 +2041,7 @@ mysql_execute_command(THD *thd) } case SQLCOM_EXECUTE: { - DBUG_PRINT("info", ("EXECUTE: %.*s\n", + DBUG_PRINT("info", ("EXECUTE: %.*s\n", lex->prepared_stmt_name.length, lex->prepared_stmt_name.str)); mysql_sql_stmt_execute(thd, &lex->prepared_stmt_name); @@ -3559,7 +3532,6 @@ error: */ int check_one_table_access(THD *thd, ulong privilege, TABLE_LIST *tables) - { if (check_access(thd, privilege, tables->db, &tables->grant.privilege,0,0)) return 1; diff --git a/sql/sql_prepare.cc b/sql/sql_prepare.cc index 11263ff0844..d9f6b333b30 100644 --- a/sql/sql_prepare.cc +++ b/sql/sql_prepare.cc @@ -777,7 +777,7 @@ static bool emb_insert_params_withlog(Prepared_statement *stmt, String *query) if (query->replace(param->pos_in_query+length, 1, *res)) DBUG_RETURN(1); - + length+= res->length()-1; } DBUG_RETURN(0); @@ -786,7 +786,7 @@ static bool emb_insert_params_withlog(Prepared_statement *stmt, String *query) #endif /*!EMBEDDED_LIBRARY*/ -/* +/* Set prepared statement parameters from user variables. SYNOPSIS insert_params_from_vars() @@ -796,78 +796,33 @@ static bool emb_insert_params_withlog(Prepared_statement *stmt, String *query) query Ignored */ -static bool insert_params_from_vars(Prepared_statement *stmt, - List& varnames, +static bool insert_params_from_vars(Prepared_statement *stmt, + List& varnames, String *query __attribute__((unused))) { Item_param **begin= stmt->param_array; Item_param **end= begin + stmt->param_count; user_var_entry *entry; LEX_STRING *varname; - DBUG_ENTER("insert_params_from_vars"); - List_iterator var_it(varnames); + DBUG_ENTER("insert_params_from_vars"); + for (Item_param **it= begin; it < end; ++it) { Item_param *param= *it; varname= var_it++; - if ((entry= (user_var_entry*)hash_search(&stmt->thd->user_vars, - (byte*) varname->str, - varname->length)) - && entry->value) - { - param->item_result_type= entry->type; - switch (entry->type) - { - case REAL_RESULT: - param->set_double(*(double*)entry->value); - break; - case INT_RESULT: - param->set_int(*(longlong*)entry->value, 21); - break; - case STRING_RESULT: - { - CHARSET_INFO *fromcs= entry->collation.collation; - CHARSET_INFO *tocs= stmt->thd->variables.collation_connection; - uint32 dummy_offset; - - param->value.cs_info.character_set_client= fromcs; - - /* - Setup source and destination character sets so that they - are different only if conversion is necessary: this will - make later checks easier. - */ - param->value.cs_info.final_character_set_of_str_value= - String::needs_conversion(0, fromcs, tocs, &dummy_offset) ? - tocs : fromcs; - /* - Exact value of max_length is not known unless data is converted to - charset of connection, so we have to set it later. - */ - param->item_type= Item::STRING_ITEM; - param->item_result_type= STRING_RESULT; - - if (param->set_str((const char *)entry->value, entry->length)) - DBUG_RETURN(1); - } - break; - default: - DBUG_ASSERT(0); - param->set_null(); - } - } - else - param->set_null(); - - if (param->convert_str_value(stmt->thd)) - DBUG_RETURN(1); /* out of memory */ + entry= (user_var_entry*)hash_search(&stmt->thd->user_vars, + (byte*) varname->str, + varname->length); + if (param->set_from_user_var(stmt->thd, entry) || + param->convert_str_value(stmt->thd)) + DBUG_RETURN(1); } DBUG_RETURN(0); } -/* +/* Do the same as insert_params_from_vars but also construct query text for binary log. SYNOPSIS @@ -879,14 +834,14 @@ static bool insert_params_from_vars(Prepared_statement *stmt, */ static bool insert_params_from_vars_with_log(Prepared_statement *stmt, - List& varnames, + List& varnames, String *query) { Item_param **begin= stmt->param_array; Item_param **end= begin + stmt->param_count; user_var_entry *entry; LEX_STRING *varname; - DBUG_ENTER("insert_params_from_vars"); + DBUG_ENTER("insert_params_from_vars"); List_iterator var_it(varnames); String str; @@ -902,53 +857,10 @@ static bool insert_params_from_vars_with_log(Prepared_statement *stmt, if (get_var_with_binlog(stmt->thd, *varname, &entry)) DBUG_RETURN(1); DBUG_ASSERT(entry); - if (entry->value) - { - param->item_result_type= entry->type; - switch (entry->type) - { - case REAL_RESULT: - param->set_double(*(double*)entry->value); - break; - case INT_RESULT: - param->set_int(*(longlong*)entry->value, 21); - break; - case STRING_RESULT: - { - CHARSET_INFO *fromcs= entry->collation.collation; - CHARSET_INFO *tocs= stmt->thd->variables.collation_connection; - uint32 dummy_offset; - param->value.cs_info.character_set_client= fromcs; - - /* - Setup source and destination character sets so that they - are different only if conversion is necessary: this will - make later checks easier. - */ - param->value.cs_info.final_character_set_of_str_value= - String::needs_conversion(0, fromcs, tocs, &dummy_offset) ? - tocs : fromcs; - /* - Exact value of max_length is not known unless data is converted to - charset of connection, so we have to set it later. - */ - param->item_type= Item::STRING_ITEM; - param->item_result_type= STRING_RESULT; - - if (param->set_str((const char *)entry->value, entry->length)) - DBUG_RETURN(1); - } - break; - default: - DBUG_ASSERT(0); - param->set_null(); - } - } - else - param->set_null(); - - /* Insert @'escaped-varname' instead of parameter in the query */ + if (param->set_from_user_var(stmt->thd, entry)) + DBUG_RETURN(1); + /* Insert @'escaped-varname' instead of parameter in the query */ char *buf, *ptr; str.length(0); if (str.reserve(entry->name.length*2+3)) @@ -958,15 +870,15 @@ static bool insert_params_from_vars_with_log(Prepared_statement *stmt, ptr= buf; *ptr++= '@'; *ptr++= '\''; - ptr+= - escape_string_for_mysql(&my_charset_utf8_general_ci, + ptr+= + escape_string_for_mysql(&my_charset_utf8_general_ci, ptr, entry->name.str, entry->name.length); *ptr++= '\''; str.length(ptr - buf); if (param->convert_str_value(stmt->thd)) DBUG_RETURN(1); /* out of memory */ - + if (query->replace(param->pos_in_query+length, 1, str)) DBUG_RETURN(1); length+= str.length()-1; @@ -1837,13 +1749,21 @@ static void reset_stmt_params(Prepared_statement *stmt) Executes previously prepared query. If there is any parameters, then replace markers with the data supplied from client, and then execute the query. - SYNOPSYS + SYNOPSIS mysql_stmt_execute() + thd Current thread + packet Query string + packet_length Query string length, including terminator character. */ void mysql_stmt_execute(THD *thd, char *packet, uint packet_length) { ulong stmt_id= uint4korr(packet); + /* + Query text for binary log, or empty string if the query is not put into + binary log. + */ + String expanded_query; #ifndef EMBEDDED_LIBRARY uchar *packet_end= (uchar *) packet + packet_length - 1; #endif @@ -1851,7 +1771,7 @@ void mysql_stmt_execute(THD *thd, char *packet, uint packet_length) DBUG_ENTER("mysql_stmt_execute"); packet+= 9; /* stmt_id + 5 bytes of flags */ - + if (!(stmt= find_prepared_statement(thd, stmt_id, "mysql_stmt_execute", SEND_ERROR))) DBUG_VOID_RETURN; @@ -1865,14 +1785,13 @@ void mysql_stmt_execute(THD *thd, char *packet, uint packet_length) DBUG_VOID_RETURN; } - String expanded_query; #ifndef EMBEDDED_LIBRARY if (stmt->param_count) { uchar *null_array= (uchar *) packet; if (setup_conversion_functions(stmt, (uchar **) &packet, packet_end) || stmt->set_params(stmt, null_array, (uchar *) packet, packet_end, - &expanded_query)) + &expanded_query)) goto set_params_data_err; } #else @@ -1890,7 +1809,7 @@ void mysql_stmt_execute(THD *thd, char *packet, uint packet_length) DBUG_VOID_RETURN; set_params_data_err: - reset_stmt_params(stmt); + reset_stmt_params(stmt); my_error(ER_WRONG_ARGUMENTS, MYF(0), "mysql_stmt_execute"); send_error(thd); DBUG_VOID_RETURN; @@ -1898,16 +1817,20 @@ set_params_data_err: /* - Execute prepared statement using parameter values from + Execute prepared statement using parameter values from lex->prepared_stmt_params and send result to the client using text protocol. */ void mysql_sql_stmt_execute(THD *thd, LEX_STRING *stmt_name) { Prepared_statement *stmt; + /* + Query text for binary log, or empty string if the query is not put into + binary log. + */ String expanded_query; DBUG_ENTER("mysql_sql_stmt_execute"); - + if (!(stmt= (Prepared_statement*)thd->stmt_map.find_by_name(stmt_name))) { my_error(ER_UNKNOWN_STMT_HANDLER, MYF(0), stmt_name->length, @@ -1918,20 +1841,19 @@ void mysql_sql_stmt_execute(THD *thd, LEX_STRING *stmt_name) if (stmt->param_count != thd->lex->prepared_stmt_params.elements) { - my_error(ER_WRONG_ARGUMENTS, MYF(0), "mysql_execute"); + my_error(ER_WRONG_ARGUMENTS, MYF(0), "EXECUTE"); send_error(thd); DBUG_VOID_RETURN; } - /* Item_param allows setting parameters in COM_EXECUTE only */ - thd->command= COM_EXECUTE; thd->free_list= NULL; thd->stmt_backup.set_statement(thd); thd->set_statement(stmt); - if (stmt->set_params_from_vars(stmt, thd->stmt_backup.lex->prepared_stmt_params, + if (stmt->set_params_from_vars(stmt, + thd->stmt_backup.lex->prepared_stmt_params, &expanded_query)) { - my_error(ER_WRONG_ARGUMENTS, MYF(0), "mysql_execute"); + my_error(ER_WRONG_ARGUMENTS, MYF(0), "EXECUTE"); send_error(thd); } execute_stmt(thd, stmt, &expanded_query); @@ -1945,7 +1867,7 @@ void mysql_sql_stmt_execute(THD *thd, LEX_STRING *stmt_name) execute_stmt() thd Current thread stmt Statement to execute - expanded_query If binary log is enabled, query string with parameter + expanded_query If binary log is enabled, query string with parameter placeholders replaced with actual values. Otherwise empty string. NOTES @@ -1953,7 +1875,7 @@ void mysql_sql_stmt_execute(THD *thd, LEX_STRING *stmt_name) thd->free_list is assumed to be garbage. */ -static void execute_stmt(THD *thd, Prepared_statement *stmt, +static void execute_stmt(THD *thd, Prepared_statement *stmt, String *expanded_query, bool set_context) { DBUG_ENTER("execute_stmt"); @@ -1964,9 +1886,9 @@ static void execute_stmt(THD *thd, Prepared_statement *stmt, thd->set_statement(stmt); } reset_stmt_for_execute(stmt); - - if (expanded_query->length() && - alloc_query(thd, (char *)expanded_query->ptr(), + + if (expanded_query->length() && + alloc_query(thd, (char *)expanded_query->ptr(), expanded_query->length()+1)) { my_error(ER_OUTOFMEMORY, 0, expanded_query->length()); @@ -1980,14 +1902,11 @@ static void execute_stmt(THD *thd, Prepared_statement *stmt, if (!(specialflag & SPECIAL_NO_PRIOR)) my_pthread_setprio(pthread_self(), WAIT_PRIOR); - /* - Free Items that were created during this execution of the PS by query - optimizer. - */ - free_items(thd->free_list); + /* Free Items that were created during this execution of the PS. */ + free_items(thd->free_list); cleanup_items(stmt->free_list); reset_stmt_params(stmt); - close_thread_tables(thd); // to close derived tables + close_thread_tables(thd); // to close derived tables thd->set_statement(&thd->stmt_backup); DBUG_VOID_RETURN; } @@ -2138,24 +2057,6 @@ Prepared_statement::Prepared_statement(THD *thd_arg) get_longdata_error(0) { *last_error= '\0'; - if (mysql_bin_log.is_open()) //psergey-todo: remove this! - { - set_params_from_vars= insert_params_from_vars_with_log; -#ifndef EMBEDDED_LIBRARY - set_params= insert_params_withlog; -#else - set_params_data= emb_insert_params_withlog; -#endif - } - else - { - set_params_from_vars= insert_params_from_vars; -#ifndef EMBEDDED_LIBRARY - set_params= insert_params; -#else - set_params_data= emb_insert_params; -#endif - } } void Prepared_statement::setup_set_params() diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy index 044dad5c4cb..bd39857c2d3 100644 --- a/sql/sql_yacc.yy +++ b/sql/sql_yacc.yy @@ -764,12 +764,12 @@ verb_clause: | checksum | commit | create - | deallocate + | deallocate | delete | describe | do | drop - | execute + | execute | flush | grant | handler @@ -781,7 +781,7 @@ verb_clause: | optimize | keycache | preload - | prepare + | prepare | purge | rename | repair @@ -803,7 +803,7 @@ verb_clause: ; deallocate: - DEALLOCATE_SYM PREPARE_SYM ident + DEALLOCATE_SYM PREPARE_SYM ident { THD *thd=YYTHD; LEX *lex= thd->lex; @@ -845,7 +845,7 @@ prepare_src: lex->prepared_stmt_code= $2; lex->prepared_stmt_code_is_varref= true; }; - + execute: EXECUTE_SYM ident {