diff options
Diffstat (limited to 'sql/sql_prepare.cc')
-rw-r--r-- | sql/sql_prepare.cc | 2209 |
1 files changed, 1508 insertions, 701 deletions
diff --git a/sql/sql_prepare.cc b/sql/sql_prepare.cc index 8a50d0bd50e..0f4ae04962b 100644 --- a/sql/sql_prepare.cc +++ b/sql/sql_prepare.cc @@ -15,77 +15,110 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ /********************************************************************** -This file contains the implementation of prepare and executes. +This file contains the implementation of prepared statements. -Prepare: +When one prepares a statement: - - Server gets the query from client with command 'COM_PREPARE'; + - Server gets the query from client with command 'COM_STMT_PREPARE'; in the following format: - [COM_PREPARE:1] [query] - - Parse the query and recognize any parameter markers '?' and + [COM_STMT_PREPARE:1] [query] + - Parse the query and recognize any parameter markers '?' and store its information list in lex->param_list - - Allocate a new statement for this prepare; and keep this in - 'thd->prepared_statements' pool. - - Without executing the query, return back to client the total + - Allocate a new statement for this prepare; and keep this in + 'thd->stmt_map'. + - Without executing the query, return back to client the total number of parameters along with result-set metadata information (if any) in the following format: [STMT_ID:4] [Column_count:2] [Param_count:2] + [Params meta info (stubs only for now)] (if Param_count > 0) [Columns meta info] (if Column_count > 0) - [Params meta info] (if Param_count > 0 ) (TODO : 4.1.1) - -Prepare-execute: - - - Server gets the command 'COM_EXECUTE' to execute the - previously prepared query. If there is any param markers; then client - will send the data in the following format: - [COM_EXECUTE:1] + +When one executes a statement: + + - Server gets the command 'COM_STMT_EXECUTE' to execute the + previously prepared query. If there are any parameter markers, then the + client will send the data in the following format: + [COM_STMT_EXECUTE:1] [STMT_ID:4] [NULL_BITS:(param_count+7)/8)] [TYPES_SUPPLIED_BY_CLIENT(0/1):1] [[length]data] - [[length]data] .. [[length]data]. - (Note: Except for string/binary types; all other types will not be + [[length]data] .. [[length]data]. + (Note: Except for string/binary types; all other types will not be supplied with length field) - - Replace the param items with this new data. If it is a first execute - or types altered by client; then setup the conversion routines. - - Execute the query without re-parsing and send back the results + - If it is a first execute or types of parameters were altered by client, + then setup the conversion routines. + - Assign parameter items from the supplied data. + - Execute the query without re-parsing and send back the results to client -Long data handling: +When one supplies long data for a placeholder: - - Server gets the long data in pieces with command type 'COM_LONG_DATA'. + - Server gets the long data in pieces with command type + 'COM_STMT_SEND_LONG_DATA'. - The packet recieved will have the format as: - [COM_LONG_DATA:1][STMT_ID:4][parameter_number:2][data] - - data from the packet is appended to long data value buffer for this + [COM_STMT_SEND_LONG_DATA:1][STMT_ID:4][parameter_number:2][data] + - data from the packet is appended to the long data value buffer for this placeholder. - - It's up to the client to check for read data ended. The server doesn't - care; and also server doesn't notify to the client that it got the - data or not; if there is any error; then during execute; the error - will be returned + - It's up to the client to stop supplying data chunks at any point. The + server doesn't care; also, the server doesn't notify the client whether + it got the data or not; if there is any error, then it will be returned + at statement execute. ***********************************************************************/ #include "mysql_priv.h" #include "sql_select.h" // for JOIN -#include <m_ctype.h> // for isspace() +#include "sql_cursor.h" +#include "sp_head.h" +#include "sp.h" +#include "sp_cache.h" #ifdef EMBEDDED_LIBRARY /* include MYSQL_BIND headers */ #include <mysql.h> +#else +#include <mysql_com.h> +#endif + +/* A result class used to send cursor rows using the binary protocol. */ + +class Select_fetch_protocol_prep: public select_send +{ + Protocol_prep protocol; +public: + Select_fetch_protocol_prep(THD *thd); + virtual bool send_fields(List<Item> &list, uint flags); + virtual bool send_data(List<Item> &items); + virtual bool send_eof(); +#ifdef EMBEDDED_LIBRARY + void begin_dataset() + { + protocol.begin_dataset(); + } #endif +}; /****************************************************************************** - Prepared_statement: statement which can contain placeholders + Prepared_statement: a statement that can contain placeholders ******************************************************************************/ class Prepared_statement: public Statement { public: + enum flag_values + { + IS_IN_USE= 1 + }; + THD *thd; + Select_fetch_protocol_prep result; + Protocol *protocol; Item_param **param_array; uint param_count; uint last_errno; + uint flags; char last_error[MYSQL_ERRMSG_SIZE]; #ifndef EMBEDDED_LIBRARY bool (*set_params)(Prepared_statement *st, uchar *data, uchar *data_end, @@ -93,18 +126,24 @@ public: #else bool (*set_params_data)(Prepared_statement *st, String *expanded_query); #endif - bool (*set_params_from_vars)(Prepared_statement *stmt, + bool (*set_params_from_vars)(Prepared_statement *stmt, List<LEX_STRING>& varnames, String *expanded_query); public: - Prepared_statement(THD *thd_arg); + Prepared_statement(THD *thd_arg, Protocol *protocol_arg); virtual ~Prepared_statement(); void setup_set_params(); - virtual Item_arena::Type type() const; + virtual Query_arena::Type type() const; + virtual void cleanup_stmt(); + bool set_name(LEX_STRING *name); + inline void close_cursor() { delete cursor; cursor= 0; } + + bool prepare(const char *packet, uint packet_length); + bool execute(String *expanded_query, bool open_cursor); + /* Destroy this statement */ + bool deallocate(); }; -static void execute_stmt(THD *thd, Prepared_statement *stmt, - String *expanded_query, bool set_context); /****************************************************************************** Implementation @@ -116,27 +155,38 @@ inline bool is_param_null(const uchar *pos, ulong param_no) return pos[param_no/8] & (1 << (param_no & 7)); } -enum { STMT_QUERY_LOG_LENGTH= 8192 }; +/* + Find a prepared statement in the statement map by id. + + SYNOPSIS + find_prepared_statement() + thd thread handle + id statement id + where the place from which this function is called (for + error reporting). -enum enum_send_error { DONT_SEND_ERROR= 0, SEND_ERROR }; + DESCRIPTION + Try to find a prepared statement and set THD error if it's not found. -/* - Seek prepared statement in statement map by id: returns zero if statement - was not found, pointer otherwise. + RETURN VALUE + 0 if the statement was not found, a pointer otherwise. */ static Prepared_statement * -find_prepared_statement(THD *thd, ulong id, const char *where, - enum enum_send_error se) +find_prepared_statement(THD *thd, ulong id, const char *where) { + /* + To strictly separate namespaces of SQL prepared statements and C API + prepared statements find() will return 0 if there is a named prepared + statement with such id. + */ Statement *stmt= thd->stmt_map.find(id); - if (stmt == 0 || stmt->type() != Item_arena::PREPARED_STATEMENT) + if (stmt == 0 || stmt->type() != Query_arena::PREPARED_STATEMENT) { char llbuf[22]; - my_error(ER_UNKNOWN_STMT_HANDLER, MYF(0), 22, llstr(id, llbuf), where); - if (se == SEND_ERROR) - send_error(thd); + my_error(ER_UNKNOWN_STMT_HANDLER, MYF(0), sizeof(llbuf), llstr(id, llbuf), + where); return 0; } return (Prepared_statement *) stmt; @@ -144,29 +194,40 @@ find_prepared_statement(THD *thd, ulong id, const char *where, /* - Send prepared stmt info to client after prepare + Send prepared statement id and metadata to the client after prepare. + + SYNOPSIS + send_prep_stmt() + + RETURN VALUE + 0 in case of success, 1 otherwise */ #ifndef EMBEDDED_LIBRARY static bool send_prep_stmt(Prepared_statement *stmt, uint columns) { NET *net= &stmt->thd->net; - char buff[9]; + char buff[12]; + uint tmp; DBUG_ENTER("send_prep_stmt"); buff[0]= 0; /* OK packet indicator */ int4store(buff+1, stmt->id); int2store(buff+5, columns); int2store(buff+7, stmt->param_count); + buff[9]= 0; // Guard against a 4.1 client + tmp= min(stmt->thd->total_warn_count, 65535); + int2store(buff+10, tmp); + /* Send types and names of placeholders to the client XXX: fix this nasty upcast from List<Item_param> to List<Item> */ - DBUG_RETURN(my_net_write(net, buff, sizeof(buff)) || + DBUG_RETURN(my_net_write(net, buff, sizeof(buff)) || (stmt->param_count && stmt->thd->protocol_simple.send_fields((List<Item> *) &stmt->lex->param_list, - 0))); + Protocol::SEND_EOF))); } #else static bool send_prep_stmt(Prepared_statement *stmt, @@ -176,7 +237,7 @@ static bool send_prep_stmt(Prepared_statement *stmt, thd->client_stmt_id= stmt->id; thd->client_param_count= stmt->param_count; - thd->net.last_errno= 0; + thd->clear_error(); return 0; } @@ -184,8 +245,20 @@ static bool send_prep_stmt(Prepared_statement *stmt, /* - Read the length of the parameter data and return back to - caller by positing the pointer to param data. + Read the length of the parameter data and return it back to + the caller. + + SYNOPSIS + get_param_length() + packet a pointer to the data + len remaining packet length + + DESCRIPTION + Read data length, position the packet to the first byte after it, + and return the length to the caller. + + RETURN VALUE + Length of data piece. */ #ifndef EMBEDDED_LIBRARY @@ -215,7 +288,7 @@ static ulong get_param_length(uchar **packet, ulong len) } if (len < 5) return 0; - (*packet)+=9; // Must be 254 when here + (*packet)+=9; // Must be 254 when here /* In our client-server protocol all numbers bigger than 2^24 stored as 8 bytes with uint8korr. Here we always know that @@ -230,19 +303,21 @@ static ulong get_param_length(uchar **packet, ulong len) #endif /*!EMBEDDED_LIBRARY*/ /* - Data conversion routines + Data conversion routines. + SYNOPSIS - set_param_xx() - param parameter item - pos input data buffer - len length of data in the buffer + set_param_xx() + param parameter item + pos input data buffer + len length of data in the buffer - All these functions read the data from pos, convert it to requested type - and assign to param; pos is advanced to predefined length. + DESCRIPTION + All these functions read the data from pos, convert it to requested + type and assign to param; pos is advanced to predefined length. - Make a note that the NULL handling is examined at first execution - (i.e. when input types altered) and for all subsequent executions - we don't read any values for this. + Make a note that the NULL handling is examined at first execution + (i.e. when input types altered) and for all subsequent executions + we don't read any values for this. RETURN VALUE none @@ -255,7 +330,7 @@ static void set_param_tiny(Item_param *param, uchar **pos, ulong len) return; #endif int8 value= (int8) **pos; - param->set_int(param->unsigned_flag ? (longlong) ((uint8) value) : + param->set_int(param->unsigned_flag ? (longlong) ((uint8) value) : (longlong) value, 4); *pos+= 1; } @@ -332,6 +407,13 @@ static void set_param_double(Item_param *param, uchar **pos, ulong len) *pos+= 8; } +static void set_param_decimal(Item_param *param, uchar **pos, ulong len) +{ + ulong length= get_param_length(pos, len); + param->set_decimal((char*)*pos, length); + *pos+= length; +} + #ifndef EMBEDDED_LIBRARY /* @@ -403,6 +485,7 @@ static void set_param_datetime(Item_param *param, uchar **pos, ulong len) *pos+= length; } + static void set_param_date(Item_param *param, uchar **pos, ulong len) { MYSQL_TIME tm; @@ -447,9 +530,10 @@ void set_param_time(Item_param *param, uchar **pos, ulong len) void set_param_datetime(Item_param *param, uchar **pos, ulong len) { - MYSQL_TIME *to= (MYSQL_TIME*)*pos; + MYSQL_TIME tm= *((MYSQL_TIME*)*pos); + tm.neg= 0; - param->set_time(to, MYSQL_TIMESTAMP_DATETIME, + param->set_time(&tm, MYSQL_TIMESTAMP_DATETIME, MAX_DATETIME_WIDTH * MY_CHARSET_BIN_MB_MAXLEN); } @@ -471,7 +555,7 @@ static void set_param_str(Item_param *param, uchar **pos, ulong len) } -#undef get_param_length +#undef get_param_length static void setup_one_conversion_function(THD *thd, Item_param *param, uchar param_type) @@ -507,6 +591,12 @@ static void setup_one_conversion_function(THD *thd, Item_param *param, param->item_type= Item::REAL_ITEM; param->item_result_type= REAL_RESULT; break; + case MYSQL_TYPE_DECIMAL: + case MYSQL_TYPE_NEWDECIMAL: + param->set_param_func= set_param_decimal; + param->item_type= Item::DECIMAL_ITEM; + param->item_result_type= DECIMAL_RESULT; + break; case MYSQL_TYPE_TIME: param->set_param_func= set_param_time; param->item_type= Item::STRING_ITEM; @@ -571,25 +661,52 @@ static void setup_one_conversion_function(THD *thd, Item_param *param, #ifndef EMBEDDED_LIBRARY /* - Update the parameter markers by reading data from client packet - and if binary/update log is set, generate the valid query. + Routines to assign parameters from data supplied by the client. + + DESCRIPTION + Update the parameter markers by reading data from the packet and + and generate a valid query for logging. + + NOTES + This function, along with other _withlog functions is called when one of + binary, slow or general logs is open. Logging of prepared statements in + all cases is performed by means of conventional queries: if parameter + data was supplied from C API, each placeholder in the query is + replaced with its actual value; if we're logging a [Dynamic] SQL + prepared statement, parameter markers are replaced with variable names. + Example: + mysql_stmt_prepare("UPDATE t1 SET a=a*1.25 WHERE a=?") + --> general logs gets [Prepare] UPDATE t1 SET a*1.25 WHERE a=?" + mysql_stmt_execute(stmt); + --> general and binary logs get + [Execute] UPDATE t1 SET a*1.25 WHERE a=1" + If a statement has been prepared using SQL syntax: + PREPARE stmt FROM "UPDATE t1 SET a=a*1.25 WHERE a=?" + --> general log gets + [Query] PREPARE stmt FROM "UPDATE ..." + EXECUTE stmt USING @a + --> general log gets + [Query] EXECUTE stmt USING @a; + + RETURN VALUE + 0 if success, 1 otherwise */ 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; + String str; const String *res; - DBUG_ENTER("insert_params_withlog"); + DBUG_ENTER("insert_params_withlog"); if (query->copy(stmt->query, stmt->query_length, default_charset_info)) DBUG_RETURN(1); - + for (Item_param **it= begin; it < end; ++it) { Item_param *param= *it; @@ -610,7 +727,7 @@ static bool insert_params_withlog(Prepared_statement *stmt, uchar *null_array, if (query->replace(param->pos_in_query+length, 1, *res)) DBUG_RETURN(1); - + length+= res->length()-1; } DBUG_RETURN(0); @@ -618,13 +735,13 @@ static bool insert_params_withlog(Prepared_statement *stmt, uchar *null_array, 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; - DBUG_ENTER("insert_params"); + DBUG_ENTER("insert_params"); for (Item_param **it= begin; it < end; ++it) { @@ -658,7 +775,7 @@ static bool setup_conversion_functions(Prepared_statement *stmt, if (*read_pos++) //types supplied / first execute { /* - First execute or types altered by the client, setup the + First execute or types altered by the client, setup the conversion routines for all parameters (one time) */ Item_param **it= stmt->param_array; @@ -684,6 +801,17 @@ static bool setup_conversion_functions(Prepared_statement *stmt, #else +/* + Embedded counterparts of parameter assignment routines. + + DESCRIPTION + The main difference between the embedded library and the server is + that in embedded case we don't serialize/deserialize parameters data. + Additionally, for unknown reason, the client-side flag raised for + changed types of placeholders is ignored and we simply setup conversion + functions at each execute (TODO: fix). +*/ + static bool emb_insert_params(Prepared_statement *stmt, String *expanded_query) { THD *thd= stmt->thd; @@ -706,8 +834,8 @@ static bool emb_insert_params(Prepared_statement *stmt, String *expanded_query) uchar *buff= (uchar*) client_param->buffer; param->unsigned_flag= client_param->is_unsigned; param->set_param_func(param, &buff, - client_param->length ? - *client_param->length : + client_param->length ? + *client_param->length : client_param->buffer_length); } } @@ -733,7 +861,7 @@ static bool emb_insert_params_withlog(Prepared_statement *stmt, String *query) if (query->copy(stmt->query, stmt->query_length, default_charset_info)) DBUG_RETURN(1); - + for (; it < end; ++it, ++client_param) { Item_param *param= *it; @@ -745,10 +873,10 @@ static bool emb_insert_params_withlog(Prepared_statement *stmt, String *query) else { uchar *buff= (uchar*)client_param->buffer; - param->unsigned_flag= client_param->is_unsigned; + param->unsigned_flag= client_param->is_unsigned; param->set_param_func(param, &buff, - client_param->length ? - *client_param->length : + client_param->length ? + *client_param->length : client_param->buffer_length); } } @@ -768,7 +896,8 @@ static bool emb_insert_params_withlog(Prepared_statement *stmt, String *query) /* - Set prepared statement parameters from user variables. + Assign prepared statement parameters from user variables. + SYNOPSIS insert_params_from_vars() stmt Statement @@ -806,12 +935,14 @@ static bool insert_params_from_vars(Prepared_statement *stmt, /* Do the same as insert_params_from_vars but also construct query text for binary log. + SYNOPSIS insert_params_from_vars() - stmt Statement + stmt Prepared 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 + query The query with parameter markers replaced with corresponding + user variables that were used to execute the query. */ static bool insert_params_from_vars_with_log(Prepared_statement *stmt, @@ -822,12 +953,13 @@ static bool insert_params_from_vars_with_log(Prepared_statement *stmt, Item_param **end= begin + stmt->param_count; user_var_entry *entry; LEX_STRING *varname; - DBUG_ENTER("insert_params_from_vars"); - List_iterator<LEX_STRING> var_it(varnames); String buf; const String *val; uint32 length= 0; + + DBUG_ENTER("insert_params_from_vars"); + if (query->copy(stmt->query, stmt->query_length, default_charset_info)) DBUG_RETURN(1); @@ -835,7 +967,8 @@ static bool insert_params_from_vars_with_log(Prepared_statement *stmt, { Item_param *param= *it; varname= var_it++; - if (get_var_with_binlog(stmt->thd, *varname, &entry)) + if (get_var_with_binlog(stmt->thd, stmt->lex->sql_command, + *varname, &entry)) DBUG_RETURN(1); if (param->set_from_user_var(stmt->thd, entry)) @@ -852,7 +985,8 @@ static bool insert_params_from_vars_with_log(Prepared_statement *stmt, *ptr++= '@'; *ptr++= '\''; ptr+= escape_string_for_mysql(&my_charset_utf8_general_ci, - ptr, entry->name.str, entry->name.length); + ptr, 0, entry->name.str, + entry->name.length); *ptr++= '\''; buf.length(ptr - begin); val= &buf; @@ -871,86 +1005,91 @@ static bool insert_params_from_vars_with_log(Prepared_statement *stmt, } /* - Validate INSERT statement: + Validate INSERT statement. SYNOPSIS mysql_test_insert() - stmt prepared statemen handler - tables list of tables queries + stmt prepared statement + tables global/local table list RETURN VALUE - 0 ok - 1 error, sent to the client - -1 error, not sent to client + FALSE success + TRUE error, error message is set in THD */ -static int mysql_test_insert(Prepared_statement *stmt, - TABLE_LIST *table_list, - List<Item> &fields, - List<List_item> &values_list, - List<Item> &update_fields, - List<Item> &update_values, - enum_duplicates duplic) + +static bool mysql_test_insert(Prepared_statement *stmt, + TABLE_LIST *table_list, + List<Item> &fields, + List<List_item> &values_list, + List<Item> &update_fields, + List<Item> &update_values, + enum_duplicates duplic) { THD *thd= stmt->thd; LEX *lex= stmt->lex; List_iterator_fast<List_item> its(values_list); List_item *values; - int res= -1; - TABLE_LIST *insert_table_list= - (TABLE_LIST*) lex->select_lex.table_list.first; DBUG_ENTER("mysql_test_insert"); - if ((res= insert_precheck(thd, table_list))) - DBUG_RETURN(res); + if (insert_precheck(thd, table_list)) + goto error; /* - open temporary memory pool for temporary data allocated by derived - tables & preparation procedure - Note that this is done without locks (should not be needed as we will not - access any data here) - If we would use locks, then we have to ensure we are not using - TL_WRITE_DELAYED as having two such locks can cause table corruption. + open temporary memory pool for temporary data allocated by derived + tables & preparation procedure + Note that this is done without locks (should not be needed as we will not + access any data here) + If we would use locks, then we have to ensure we are not using + TL_WRITE_DELAYED as having two such locks can cause table corruption. */ - if (open_normal_and_derived_tables(thd, table_list)) - { - DBUG_RETURN(-1); - } + if (open_normal_and_derived_tables(thd, table_list, 0)) + goto error; if ((values= its++)) { uint value_count; ulong counter= 0; + Item *unused_conds= 0; + + if (table_list->table) + { + // don't allocate insert_values + table_list->table->insert_values=(byte *)1; + } - table_list->table->insert_values=(byte *)1; // don't allocate insert_values - if ((res= mysql_prepare_insert(thd, table_list, insert_table_list, - insert_table_list, - table_list->table, fields, values, - update_fields, update_values, duplic))) + if (mysql_prepare_insert(thd, table_list, table_list->table, + fields, values, update_fields, update_values, + duplic, &unused_conds, FALSE)) goto error; value_count= values->elements; its.rewind(); + if (table_list->lock_type == TL_WRITE_DELAYED && + !(table_list->table->file->table_flags() & HA_CAN_INSERT_DELAYED)) + { + my_error(ER_ILLEGAL_HA, MYF(0), (table_list->view ? + table_list->view_name.str : + table_list->table_name)); + goto error; + } while ((values= its++)) { counter++; if (values->elements != value_count) { - my_printf_error(ER_WRONG_VALUE_COUNT_ON_ROW, - ER(ER_WRONG_VALUE_COUNT_ON_ROW), - MYF(0), counter); + my_error(ER_WRONG_VALUE_COUNT_ON_ROW, MYF(0), counter); goto error; } - if (setup_fields(thd, 0, insert_table_list, *values, 0, 0, 0)) - goto error; + if (setup_fields(thd, 0, *values, 0, 0, 0)) + goto error; } } + DBUG_RETURN(FALSE); - res= 0; error: - lex->unit.cleanup(); - table_list->table->insert_values=0; - DBUG_RETURN(res); + /* insert_values is cleared in open_table */ + DBUG_RETURN(TRUE); } @@ -959,211 +1098,242 @@ error: SYNOPSIS mysql_test_update() - stmt prepared statemen handler - tables list of tables queries + stmt prepared statement + tables list of tables used in this query RETURN VALUE - 0 success - 1 error, sent to client - -1 error, not sent to client + 0 success + 1 error, error message is set in THD + 2 convert to multi_update */ + static int mysql_test_update(Prepared_statement *stmt, - TABLE_LIST *table_list) + TABLE_LIST *table_list) { int res; THD *thd= stmt->thd; + uint table_count= 0; SELECT_LEX *select= &stmt->lex->select_lex; +#ifndef NO_EMBEDDED_ACCESS_CHECKS + uint want_privilege; +#endif + bool need_reopen; DBUG_ENTER("mysql_test_update"); - if ((res= update_precheck(thd, table_list))) - DBUG_RETURN(res); + if (update_precheck(thd, table_list)) + goto error; - if (open_and_lock_tables(thd, table_list)) - res= -1; - else + for ( ; ; ) { - TABLE_LIST *update_table_list= (TABLE_LIST *)select->table_list.first; - if (!(res= mysql_prepare_update(thd, table_list, - update_table_list, - &select->where, - select->order_list.elements, - (ORDER *) select->order_list.first))) + if (open_tables(thd, &table_list, &table_count, 0)) + goto error; + + if (table_list->multitable_view) { - if (setup_fields(thd, 0, update_table_list, - select->item_list, 1, 0, 0) || - setup_fields(thd, 0, update_table_list, - stmt->lex->value_list, 0, 0, 0)) - res= -1; + DBUG_ASSERT(table_list->view != 0); + DBUG_PRINT("info", ("Switch to multi-update")); + /* pass counter value */ + thd->lex->table_count= table_count; + /* convert to multiupdate */ + DBUG_RETURN(2); } - stmt->lex->unit.cleanup(); + + if (!lock_tables(thd, table_list, table_count, &need_reopen)) + break; + if (!need_reopen) + goto error; + close_tables_for_reopen(thd, &table_list); } - /* TODO: here we should send types of placeholders to the client. */ - DBUG_RETURN(res); + + /* + thd->fill_derived_tables() is false here for sure (because it is + preparation of PS, so we even do not check it). + */ + if (mysql_handle_derived(thd->lex, &mysql_derived_prepare)) + goto error; + +#ifndef NO_EMBEDDED_ACCESS_CHECKS + /* TABLE_LIST contain right privilages request */ + want_privilege= table_list->grant.want_privilege; +#endif + + if (mysql_prepare_update(thd, table_list, &select->where, + select->order_list.elements, + (ORDER *) select->order_list.first)) + goto error; + +#ifndef NO_EMBEDDED_ACCESS_CHECKS + table_list->grant.want_privilege= want_privilege; + table_list->table->grant.want_privilege= want_privilege; + table_list->register_want_access(want_privilege); +#endif + thd->lex->select_lex.no_wrap_view_item= TRUE; + res= setup_fields(thd, 0, select->item_list, 1, 0, 0); + thd->lex->select_lex.no_wrap_view_item= FALSE; + if (res) + goto error; +#ifndef NO_EMBEDDED_ACCESS_CHECKS + /* Check values */ + table_list->grant.want_privilege= + table_list->table->grant.want_privilege= + (SELECT_ACL & ~table_list->table->grant.privilege); + table_list->register_want_access(SELECT_ACL); +#endif + if (setup_fields(thd, 0, stmt->lex->value_list, 0, 0, 0)) + goto error; + /* TODO: here we should send types of placeholders to the client. */ + DBUG_RETURN(0); +error: + DBUG_RETURN(1); } /* - Validate DELETE statement + Validate DELETE statement. SYNOPSIS mysql_test_delete() - stmt prepared statemen handler - tables list of tables queries + stmt prepared statement + tables list of tables used in this query RETURN VALUE - 0 success - 1 error, sent to client - -1 error, not sent to client + FALSE success + TRUE error, error message is set in THD */ -static int mysql_test_delete(Prepared_statement *stmt, - TABLE_LIST *table_list) + +static bool mysql_test_delete(Prepared_statement *stmt, + TABLE_LIST *table_list) { - int res; THD *thd= stmt->thd; LEX *lex= stmt->lex; DBUG_ENTER("mysql_test_delete"); - if ((res= delete_precheck(thd, table_list))) - DBUG_RETURN(res); + if (delete_precheck(thd, table_list) || + open_and_lock_tables(thd, table_list)) + goto error; - if (open_and_lock_tables(thd, table_list)) - res= -1; - else + if (!table_list->table) { - res= mysql_prepare_delete(thd, table_list, &lex->select_lex.where); - lex->unit.cleanup(); + my_error(ER_VIEW_DELETE_MERGE_VIEW, MYF(0), + table_list->view_db.str, table_list->view_name.str); + goto error; } - /* TODO: here we should send types of placeholders to the client. */ - DBUG_RETURN(res); + + DBUG_RETURN(mysql_prepare_delete(thd, table_list, &lex->select_lex.where)); +error: + DBUG_RETURN(TRUE); } /* Validate SELECT statement. - In case of success, if this query is not EXPLAIN, send column list info - back to client. SYNOPSIS mysql_test_select() - stmt prepared statemen handler - tables list of tables queries + stmt prepared statement + tables list of tables used in the query + + DESCRIPTION + In case of success, if this query is not EXPLAIN, send column list info + back to the client. RETURN VALUE - 0 success - 1 error, sent to client - -1 error, not sent to client + 0 success + 1 error, error message is set in THD + 2 success, and statement metadata has been sent */ static int mysql_test_select(Prepared_statement *stmt, - TABLE_LIST *tables, bool text_protocol) + TABLE_LIST *tables, bool text_protocol) { THD *thd= stmt->thd; LEX *lex= stmt->lex; SELECT_LEX_UNIT *unit= &lex->unit; - int result= 1; DBUG_ENTER("mysql_test_select"); + lex->select_lex.context.resolve_in_select_list= TRUE; + #ifndef NO_EMBEDDED_ACCESS_CHECKS ulong privilege= lex->exchange ? SELECT_ACL | FILE_ACL : SELECT_ACL; if (tables) { if (check_table_access(thd, privilege, tables,0)) - DBUG_RETURN(1); + goto error; } - else if (check_access(thd, privilege, any_db,0,0,0)) - DBUG_RETURN(1); + else if (check_access(thd, privilege, any_db,0,0,0,0)) + goto error; #endif if (!lex->result && !(lex->result= new (stmt->mem_root) select_send)) { - send_error(thd); - goto err; + my_error(ER_OUTOFMEMORY, MYF(0), sizeof(select_send)); + goto error; } if (open_and_lock_tables(thd, tables)) - { - send_error(thd); - goto err; - } + goto error; thd->used_tables= 0; // Updated by setup_fields - // JOIN::prepare calls - if (unit->prepare(thd, 0, 0, "")) - { - send_error(thd); - goto err_prep; - } - if (!text_protocol) + /* + JOIN::prepare calls + It is not SELECT COMMAND for sure, so setup_tables will be called as + usual, and we pass 0 as setup_tables_done_option + */ + if (unit->prepare(thd, 0, 0)) + goto error; + if (!lex->describe && !text_protocol) { - if (lex->describe) - { - if (send_prep_stmt(stmt, 0) || thd->protocol->flush()) - goto err_prep; - } - else - { - /* Make copy of item list, as change_columns may change it */ - List<Item> fields(lex->select_lex.item_list); + /* Make copy of item list, as change_columns may change it */ + List<Item> fields(lex->select_lex.item_list); - /* Change columns if a procedure like analyse() */ - if (unit->last_procedure && - unit->last_procedure->change_columns(fields)) - goto err_prep; + /* Change columns if a procedure like analyse() */ + if (unit->last_procedure && unit->last_procedure->change_columns(fields)) + goto error; - /* - We can use lex->result as it should've been - prepared in unit->prepare call above. - */ - if (send_prep_stmt(stmt, lex->result->field_count(fields)) || - lex->result->send_fields(fields, 0) || - thd->protocol->flush()) - goto err_prep; - } + /* + We can use lex->result as it should've been prepared in + unit->prepare call above. + */ + if (send_prep_stmt(stmt, lex->result->field_count(fields)) || + lex->result->send_fields(fields, Protocol::SEND_EOF) || + thd->protocol->flush()) + goto error; + DBUG_RETURN(2); } - result= 0; // ok - -err_prep: - unit->cleanup(); -err: - DBUG_RETURN(result); + DBUG_RETURN(0); +error: + DBUG_RETURN(1); } /* - Validate and prepare for execution DO statement expressions + Validate and prepare for execution DO statement expressions. SYNOPSIS mysql_test_do_fields() - stmt prepared statemen handler - tables list of tables queries - values list of expressions + stmt prepared statement + tables list of tables used in this query + values list of expressions RETURN VALUE - 0 success - 1 error, sent to client - -1 error, not sent to client + FALSE success + TRUE error, error message is set in THD */ -static int mysql_test_do_fields(Prepared_statement *stmt, - TABLE_LIST *tables, - List<Item> *values) +static bool mysql_test_do_fields(Prepared_statement *stmt, + TABLE_LIST *tables, + List<Item> *values) { - DBUG_ENTER("mysql_test_do_fields"); THD *thd= stmt->thd; - int res= 0; - if (tables && (res= check_table_access(thd, SELECT_ACL, tables, 0))) - DBUG_RETURN(res); - if (tables && (res= open_and_lock_tables(thd, tables))) - { - DBUG_RETURN(res); - } - res= setup_fields(thd, 0, 0, *values, 0, 0, 0); - stmt->lex->unit.cleanup(); - if (res) - DBUG_RETURN(-1); - DBUG_RETURN(0); + DBUG_ENTER("mysql_test_do_fields"); + if (tables && check_table_access(thd, SELECT_ACL, tables, 0)) + DBUG_RETURN(TRUE); + + if (open_and_lock_tables(thd, tables)) + DBUG_RETURN(TRUE); + DBUG_RETURN(setup_fields(thd, 0, *values, 0, 0, 0)); } @@ -1172,42 +1342,36 @@ static int mysql_test_do_fields(Prepared_statement *stmt, SYNOPSIS mysql_test_set_fields() - stmt prepared statemen handler - tables list of tables queries - values list of expressions + stmt prepared statement + tables list of tables used in this query + values list of expressions RETURN VALUE - 0 success - 1 error, sent to client - -1 error, not sent to client + FALSE success + TRUE error, error message is set in THD */ -static int mysql_test_set_fields(Prepared_statement *stmt, - TABLE_LIST *tables, - List<set_var_base> *var_list) + +static bool mysql_test_set_fields(Prepared_statement *stmt, + TABLE_LIST *tables, + List<set_var_base> *var_list) { DBUG_ENTER("mysql_test_set_fields"); List_iterator_fast<set_var_base> it(*var_list); THD *thd= stmt->thd; set_var_base *var; - int res= 0; - if (tables && (res= check_table_access(thd, SELECT_ACL, tables, 0))) - DBUG_RETURN(res); - - if (tables && (res= open_and_lock_tables(thd, tables))) + if (tables && check_table_access(thd, SELECT_ACL, tables, 0) || + open_and_lock_tables(thd, tables)) goto error; + while ((var= it++)) { if (var->light_check(thd)) - { - stmt->lex->unit.cleanup(); - res= -1; goto error; - } } + DBUG_RETURN(FALSE); error: - stmt->lex->unit.cleanup(); - DBUG_RETURN(res); + DBUG_RETURN(TRUE); } @@ -1215,36 +1379,76 @@ error: Check internal SELECT of the prepared command SYNOPSIS - select_like_statement_test() - stmt - prepared table handler - tables - global list of tables + select_like_stmt_test() + stmt prepared statement + specific_prepare function of command specific prepare + setup_tables_done_option options to be passed to LEX::unit.prepare() + + NOTE + This function won't directly open tables used in select. They should + be opened either by calling function (and in this case you probably + should use select_like_stmt_test_with_open_n_lock()) or by + "specific_prepare" call (like this happens in case of multi-update). RETURN VALUE - 0 success - 1 error, sent to client - -1 error, not sent to client + FALSE success + TRUE error, error message is set in THD */ -static int select_like_statement_test(Prepared_statement *stmt, - TABLE_LIST *tables) + +static bool select_like_stmt_test(Prepared_statement *stmt, + bool (*specific_prepare)(THD *thd), + ulong setup_tables_done_option) { - DBUG_ENTER("select_like_statement_test"); + DBUG_ENTER("select_like_stmt_test"); THD *thd= stmt->thd; LEX *lex= stmt->lex; - int res= 0; - if (tables && (res= open_and_lock_tables(thd, tables))) - goto end; + lex->select_lex.context.resolve_in_select_list= TRUE; + + if (specific_prepare && (*specific_prepare)(thd)) + DBUG_RETURN(TRUE); thd->used_tables= 0; // Updated by setup_fields - // JOIN::prepare calls - if (lex->unit.prepare(thd, 0, 0, "")) - { - res= thd->net.report_error ? -1 : 1; - } -end: - lex->unit.cleanup(); - DBUG_RETURN(res); + /* Calls JOIN::prepare */ + DBUG_RETURN(lex->unit.prepare(thd, 0, setup_tables_done_option)); +} + +/* + Check internal SELECT of the prepared command (with opening and + locking of used tables). + + SYNOPSIS + select_like_stmt_test_with_open_n_lock() + stmt prepared statement + tables list of tables to be opened and locked + before calling specific_prepare function + specific_prepare function of command specific prepare + setup_tables_done_option options to be passed to LEX::unit.prepare() + + RETURN VALUE + FALSE success + TRUE error +*/ + +static bool +select_like_stmt_test_with_open_n_lock(Prepared_statement *stmt, + TABLE_LIST *tables, + bool (*specific_prepare)(THD *thd), + ulong setup_tables_done_option) +{ + DBUG_ENTER("select_like_stmt_test_with_open_n_lock"); + + /* + We should not call LEX::unit.cleanup() after this open_and_lock_tables() + call because we don't allow prepared EXPLAIN yet so derived tables will + clean up after themself. + */ + if (open_and_lock_tables(stmt->thd, tables)) + DBUG_RETURN(TRUE); + + DBUG_RETURN(select_like_stmt_test(stmt, specific_prepare, + setup_tables_done_option)); } @@ -1253,168 +1457,240 @@ end: SYNOPSIS mysql_test_create_table() - stmt prepared statemen handler - tables list of tables queries + stmt prepared statement + tables list of tables used in this query RETURN VALUE - 0 success - 1 error, sent to client - -1 error, not sent to client + FALSE success + TRUE error, error message is set in THD */ -static int mysql_test_create_table(Prepared_statement *stmt, - TABLE_LIST *tables) + +static bool mysql_test_create_table(Prepared_statement *stmt) { DBUG_ENTER("mysql_test_create_table"); THD *thd= stmt->thd; LEX *lex= stmt->lex; SELECT_LEX *select_lex= &lex->select_lex; - int res= 0; - + bool res= FALSE; /* Skip first table, which is the table we are creating */ - TABLE_LIST *create_table, *create_table_local; - tables= lex->unlink_first_table(tables, &create_table, - &create_table_local); + bool link_to_local; + TABLE_LIST *create_table= lex->unlink_first_table(&link_to_local); + TABLE_LIST *tables= lex->query_tables; + + if (create_table_precheck(thd, tables, create_table)) + DBUG_RETURN(TRUE); - if (!(res= create_table_precheck(thd, tables, create_table)) && - select_lex->item_list.elements) + if (select_lex->item_list.elements) { - select_lex->resolve_mode= SELECT_LEX::SELECT_MODE; - res= select_like_statement_test(stmt, tables); - select_lex->resolve_mode= SELECT_LEX::NOMATTER_MODE; + select_lex->context.resolve_in_select_list= TRUE; + res= select_like_stmt_test_with_open_n_lock(stmt, tables, 0, 0); } /* put tables back for PS rexecuting */ - tables= lex->link_first_table_back(tables, create_table, - create_table_local); + lex->link_first_table_back(create_table, link_to_local); DBUG_RETURN(res); } /* - Validate and prepare for execution multi update statement + Validate and prepare for execution a multi update statement. SYNOPSIS mysql_test_multiupdate() - stmt prepared statemen handler - tables list of tables queries + stmt prepared statement + tables list of tables used in this query + converted converted to multi-update from usual update RETURN VALUE - 0 success - 1 error, sent to client - -1 error, not sent to client + FALSE success + TRUE error, error message is set in THD */ -static int mysql_test_multiupdate(Prepared_statement *stmt, - TABLE_LIST *tables) + +static bool mysql_test_multiupdate(Prepared_statement *stmt, + TABLE_LIST *tables, + bool converted) { - int res; - if ((res= multi_update_precheck(stmt->thd, tables))) - return res; - return select_like_statement_test(stmt, tables); + /* if we switched from normal update, rights are checked */ + if (!converted && multi_update_precheck(stmt->thd, tables)) + return TRUE; + + return select_like_stmt_test(stmt, &mysql_multi_update_prepare, + OPTION_SETUP_TABLES_DONE); } /* - Validate and prepare for execution multi delete statement + Validate and prepare for execution a multi delete statement. SYNOPSIS mysql_test_multidelete() - stmt prepared statemen handler - tables list of tables queries + stmt prepared statement + tables list of tables used in this query RETURN VALUE - 0 success - 1 error, sent to client - -1 error, not sent to client + FALSE success + TRUE error, error message in THD is set. */ -static int mysql_test_multidelete(Prepared_statement *stmt, - TABLE_LIST *tables) + +static bool mysql_test_multidelete(Prepared_statement *stmt, + TABLE_LIST *tables) { - int res; stmt->thd->lex->current_select= &stmt->thd->lex->select_lex; if (add_item_to_list(stmt->thd, new Item_null())) - return -1; + { + my_error(ER_OUTOFMEMORY, MYF(0), 0); + goto error; + } - uint fake_counter; - if ((res= multi_delete_precheck(stmt->thd, tables, &fake_counter))) - return res; - return select_like_statement_test(stmt, tables); + if (multi_delete_precheck(stmt->thd, tables) || + select_like_stmt_test_with_open_n_lock(stmt, tables, + &mysql_multi_delete_prepare, + OPTION_SETUP_TABLES_DONE)) + goto error; + if (!tables->table) + { + my_error(ER_VIEW_DELETE_MERGE_VIEW, MYF(0), + tables->view_db.str, tables->view_name.str); + goto error; + } + return FALSE; +error: + return TRUE; } /* - Validate and prepare for execution INSERT ... SELECT statement + Wrapper for mysql_insert_select_prepare, to make change of local tables + after open_and_lock_tables() call. SYNOPSIS - mysql_test_insert_select() - stmt prepared statemen handler - tables list of tables queries + mysql_insert_select_prepare_tester() + thd thread handle - RETURN VALUE - 0 success - 1 error, sent to client - -1 error, not sent to client + NOTE + We need to remove the first local table after open_and_lock_tables, + because mysql_handle_derived uses local tables lists. */ -static int mysql_test_insert_select(Prepared_statement *stmt, - TABLE_LIST *tables) + +static bool mysql_insert_select_prepare_tester(THD *thd) { - int res; - LEX *lex= stmt->lex; - if ((res= insert_precheck(stmt->thd, tables))) - return res; - TABLE_LIST *first_local_table= - (TABLE_LIST *)lex->select_lex.table_list.first; + TABLE_LIST *first; + bool res; + SELECT_LEX *first_select= &thd->lex->select_lex; /* Skip first table, which is the table we are inserting in */ - lex->select_lex.table_list.first= (byte*) first_local_table->next; + first_select->table_list.first= (byte*)(first= + ((TABLE_LIST*)first_select-> + table_list.first)->next_local); + res= mysql_insert_select_prepare(thd); /* insert/replace from SELECT give its SELECT_LEX for SELECT, and item_list belong to SELECT */ - lex->select_lex.resolve_mode= SELECT_LEX::SELECT_MODE; - res= select_like_statement_test(stmt, tables); - /* revert changes*/ + thd->lex->select_lex.context.resolve_in_select_list= TRUE; + thd->lex->select_lex.context.table_list= first; + return res; +} + + +/* + Validate and prepare for execution INSERT ... SELECT statement. + + SYNOPSIS + mysql_test_insert_select() + stmt prepared statement + tables list of tables used in this query + + RETURN VALUE + FALSE success + TRUE error, error message is set in THD +*/ + +static bool mysql_test_insert_select(Prepared_statement *stmt, + TABLE_LIST *tables) +{ + int res; + LEX *lex= stmt->lex; + TABLE_LIST *first_local_table; + + if (tables->table) + { + // don't allocate insert_values + tables->table->insert_values=(byte *)1; + } + + if (insert_precheck(stmt->thd, tables)) + return 1; + + /* store it, because mysql_insert_select_prepare_tester change it */ + first_local_table= (TABLE_LIST *)lex->select_lex.table_list.first; + DBUG_ASSERT(first_local_table != 0); + + res= + select_like_stmt_test_with_open_n_lock(stmt, tables, + &mysql_insert_select_prepare_tester, + OPTION_SETUP_TABLES_DONE); + /* revert changes made by mysql_insert_select_prepare_tester */ lex->select_lex.table_list.first= (byte*) first_local_table; - lex->select_lex.resolve_mode= SELECT_LEX::INSERT_MODE; return res; } /* - Send the prepare query results back to client + Perform semantic analysis of the parsed tree and send a response packet + to the client. + SYNOPSIS - send_prepare_results() - stmt prepared statement + check_prepared_statement() + stmt prepared statement + + DESCRIPTION + This function + - opens all tables and checks access rights + - validates semantics of statement columns and SQL functions + by calling fix_fields. + RETURN VALUE - 0 success - 1 error, sent to client + FALSE success, statement metadata is sent to client + TRUE error, error message is set in THD (but not sent) */ -static int send_prepare_results(Prepared_statement *stmt, bool text_protocol) -{ + +static bool check_prepared_statement(Prepared_statement *stmt, + bool text_protocol) +{ THD *thd= stmt->thd; LEX *lex= stmt->lex; SELECT_LEX *select_lex= &lex->select_lex; - TABLE_LIST *tables=(TABLE_LIST*) select_lex->table_list.first; + TABLE_LIST *tables; enum enum_sql_command sql_command= lex->sql_command; int res= 0; - DBUG_ENTER("send_prepare_results"); + DBUG_ENTER("check_prepared_statement"); DBUG_PRINT("enter",("command: %d, param_count: %ld", sql_command, stmt->param_count)); - if ((&lex->select_lex != lex->all_selects_list || - lex->time_zone_tables_used) && - lex->unit.create_total_list(thd, lex, &tables)) - DBUG_RETURN(1); + lex->first_lists_tables_same(); + tables= lex->query_tables; + + /* set context for commands which do not use setup_tables */ + lex->select_lex.context.resolve_in_table_list_only(select_lex-> + get_table_list()); switch (sql_command) { case SQLCOM_REPLACE: case SQLCOM_INSERT: res= mysql_test_insert(stmt, tables, lex->field_list, - lex->many_values, - select_lex->item_list, lex->value_list, - lex->duplicates); + lex->many_values, + select_lex->item_list, lex->value_list, + lex->duplicates); break; case SQLCOM_UPDATE: res= mysql_test_update(stmt, tables); + /* mysql_test_update returns 2 if we need to switch to multi-update */ + if (res != 2) + break; + + case SQLCOM_UPDATE_MULTI: + res= mysql_test_multiupdate(stmt, tables, res == 2); break; case SQLCOM_DELETE: @@ -1422,15 +1698,17 @@ static int send_prepare_results(Prepared_statement *stmt, bool text_protocol) break; case SQLCOM_SELECT: - if ((res= mysql_test_select(stmt, tables, text_protocol))) - goto error; - /* Statement and field info has already been sent */ - DBUG_RETURN(0); - + res= mysql_test_select(stmt, tables, text_protocol); + if (res == 2) + { + /* Statement and field info has already been sent */ + DBUG_RETURN(FALSE); + } + break; case SQLCOM_CREATE_TABLE: - res= mysql_test_create_table(stmt, tables); + res= mysql_test_create_table(stmt); break; - + case SQLCOM_DO: res= mysql_test_do_fields(stmt, tables, lex->insert_list); break; @@ -1442,10 +1720,6 @@ static int send_prepare_results(Prepared_statement *stmt, bool text_protocol) case SQLCOM_DELETE_MULTI: res= mysql_test_multidelete(stmt, tables); break; - - case SQLCOM_UPDATE_MULTI: - res= mysql_test_multiupdate(stmt, tables); - break; case SQLCOM_INSERT_SELECT: case SQLCOM_REPLACE_SELECT: @@ -1470,23 +1744,27 @@ static int send_prepare_results(Prepared_statement *stmt, bool text_protocol) case SQLCOM_SHOW_GRANTS: case SQLCOM_DROP_TABLE: case SQLCOM_RENAME_TABLE: + case SQLCOM_ALTER_TABLE: + case SQLCOM_COMMIT: + case SQLCOM_CREATE_INDEX: + case SQLCOM_DROP_INDEX: + case SQLCOM_ROLLBACK: + case SQLCOM_TRUNCATE: + case SQLCOM_CALL: + case SQLCOM_CREATE_VIEW: + case SQLCOM_DROP_VIEW: break; default: - /* - All other is not supported yet - */ - res= -1; - my_error(ER_UNSUPPORTED_PS, MYF(0)); + /* All other statements are not supported yet. */ + my_message(ER_UNSUPPORTED_PS, ER(ER_UNSUPPORTED_PS), MYF(0)); goto error; } if (res == 0) - DBUG_RETURN(text_protocol? 0 : (send_prep_stmt(stmt, 0) || - thd->protocol->flush())); + DBUG_RETURN(text_protocol? FALSE : (send_prep_stmt(stmt, 0) || + thd->protocol->flush())); error: - if (res < 0) - send_error(thd, thd->killed ? ER_SERVER_SHUTDOWN : 0); - DBUG_RETURN(1); + DBUG_RETURN(TRUE); } /* @@ -1498,15 +1776,13 @@ error: static bool init_param_array(Prepared_statement *stmt) { LEX *lex= stmt->lex; - THD *thd= stmt->thd; if ((stmt->param_count= lex->param_list.elements)) { if (stmt->param_count > (uint) UINT_MAX16) { /* Error code to be defined in 5.0 */ - send_error(thd, ER_UNKNOWN_ERROR, - "Prepared statement contains too many placeholders."); - return 1; + my_message(ER_PS_MANY_PARAM, ER(ER_PS_MANY_PARAM), MYF(0)); + return TRUE; } Item_param **to; List_iterator<Item_param> param_iterator(lex->param_list); @@ -1515,10 +1791,7 @@ static bool init_param_array(Prepared_statement *stmt) alloc_root(stmt->thd->mem_root, sizeof(Item_param*) * stmt->param_count); if (!stmt->param_array) - { - send_error(thd, ER_OUT_OF_RESOURCES); - return 1; - } + return TRUE; for (to= stmt->param_array; to < stmt->param_array + stmt->param_count; ++to) @@ -1526,227 +1799,363 @@ static bool init_param_array(Prepared_statement *stmt) *to= param_iterator++; } } - return 0; + return FALSE; } + /* - Given a query string with parameter markers, create a Prepared Statement - from it and send PS info back to the client. - + COM_STMT_PREPARE handler. + SYNOPSIS mysql_stmt_prepare() - 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 statements text protocol is - used. - RETURN - 0 OK, statement prepared successfully - other Error - + packet query to be prepared + packet_length query string length, including ignored + trailing NULL or quote char. + + DESCRIPTION + Given a query string with parameter markers, create a prepared + statement from it and send PS info back to the client. + 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. + 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. 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 + 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. - + + RETURN VALUE + none: in case of success a new statement id and metadata is sent + to the client, otherwise an error message is set in THD. */ -int mysql_stmt_prepare(THD *thd, char *packet, uint packet_length, - LEX_STRING *name) +void mysql_stmt_prepare(THD *thd, const char *packet, uint packet_length) { - LEX *lex; - Prepared_statement *stmt= new Prepared_statement(thd); - int error; + Prepared_statement *stmt; + bool error; DBUG_ENTER("mysql_stmt_prepare"); DBUG_PRINT("prep_query", ("%s", packet)); - /* - If this is an SQLCOM_PREPARE, we also increase Com_prepare_sql. - However, it seems handy if com_stmt_prepare is increased always, - no matter what kind of prepare is processed. - */ - statistic_increment(com_stmt_prepare, &LOCK_status); - - if (stmt == 0) - { - send_error(thd, ER_OUT_OF_RESOURCES); - DBUG_RETURN(1); - } + /* First of all clear possible warnings from the previous command */ + mysql_reset_thd_for_next_command(thd); - if (name) - { - stmt->name.length= name->length; - if (!(stmt->name.str= memdup_root(stmt->mem_root, (char*)name->str, - name->length))) - { - delete stmt; - send_error(thd, ER_OUT_OF_RESOURCES); - DBUG_RETURN(1); - } - } + if (! (stmt= new Prepared_statement(thd, &thd->protocol_prep))) + DBUG_VOID_RETURN; /* out of memory: error is set in Sql_alloc */ if (thd->stmt_map.insert(stmt)) { delete stmt; - send_error(thd, ER_OUT_OF_RESOURCES); - DBUG_RETURN(1); - } - - thd->set_n_backup_statement(stmt, &thd->stmt_backup); - thd->set_n_backup_item_arena(stmt, &thd->stmt_backup); - - if (alloc_query(thd, packet, packet_length)) - { - thd->restore_backup_statement(stmt, &thd->stmt_backup); - thd->restore_backup_item_arena(stmt, &thd->stmt_backup); - /* Statement map deletes statement on erase */ - thd->stmt_map.erase(stmt); - send_error(thd, ER_OUT_OF_RESOURCES); - DBUG_RETURN(1); + DBUG_VOID_RETURN; /* out of memory */ } - mysql_log.write(thd, thd->command, "[%lu] %s", stmt->id, packet); - - thd->current_arena= stmt; - mysql_init_query(thd, (uchar *) thd->query, thd->query_length); /* Reset warnings from previous command */ - mysql_reset_errors(thd); - lex= thd->lex; - lex->safe_to_cache_query= 0; + mysql_reset_errors(thd, 0); + sp_cache_flush_obsolete(&thd->sp_proc_cache); + sp_cache_flush_obsolete(&thd->sp_func_cache); - error= yyparse((void *)thd) || thd->is_fatal_error || - thd->net.report_error || init_param_array(stmt); - /* - While doing context analysis of the query (in send_prepare_results) we - allocate a lot of additional memory: for open tables, JOINs, derived - tables, etc. Let's save a snapshot of current parse tree to the - statement and restore original THD. In cases when some tree - transformation can be reused on execute, we set again thd->mem_root from - stmt->mem_root (see setup_wild for one place where we do that). - */ - thd->restore_backup_item_arena(stmt, &thd->stmt_backup); + if (!(specialflag & SPECIAL_NO_PRIOR)) + my_pthread_setprio(pthread_self(),QUERY_PRIOR); - if (!error) - error= send_prepare_results(stmt, test(name)); + error= stmt->prepare(packet, packet_length); - /* restore to WAIT_PRIOR: QUERY_PRIOR is set inside alloc_query */ if (!(specialflag & SPECIAL_NO_PRIOR)) my_pthread_setprio(pthread_self(),WAIT_PRIOR); - lex_end(lex); - thd->restore_backup_statement(stmt, &thd->stmt_backup); - cleanup_items(stmt->free_list); - close_thread_tables(thd); - free_items(thd->free_list); - thd->rollback_item_tree_changes(); - thd->free_list= 0; - thd->current_arena= thd; if (error) { /* Statement map deletes statement on erase */ thd->stmt_map.erase(stmt); - stmt= NULL; - if (thd->net.report_error) - send_error(thd); - /* otherwise the error is sent inside yyparse/send_prepare_results */ } else + mysql_log.write(thd, COM_STMT_PREPARE, "[%lu] %s", stmt->id, packet); + + /* check_prepared_statemnt sends the metadata packet in case of success */ + DBUG_VOID_RETURN; +} + +/* + SYNOPSIS + get_dynamic_sql_string() + lex in main lex + query_len out length of the SQL statement (is set only + in case of success) + + DESCRIPTION + Get an SQL statement text from a user variable or from plain + text. If the statement is plain text, just assign the + pointers, otherwise allocate memory in thd->mem_root and copy + the contents of the variable, possibly with character + set conversion. + + RETURN VALUE + non-zero success, 0 in case of error (out of memory) +*/ + +static const char *get_dynamic_sql_string(LEX *lex, uint *query_len) +{ + THD *thd= lex->thd; + char *query_str= 0; + + if (lex->prepared_stmt_code_is_varref) { - stmt->setup_set_params(); - SELECT_LEX *sl= stmt->lex->all_selects_list; - for (; sl; sl= sl->next_select_in_list()) + /* This is PREPARE stmt FROM or EXECUTE IMMEDIATE @var. */ + String str; + CHARSET_INFO *to_cs= thd->variables.collation_connection; + bool needs_conversion; + user_var_entry *entry; + String *var_value= &str; + uint32 unused, len; + /* + 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 be uniform. + */ + if ((entry= + (user_var_entry*)hash_search(&thd->user_vars, + (byte*)lex->prepared_stmt_code.str, + lex->prepared_stmt_code.length)) + && entry->value) { + my_bool is_var_null; + var_value= entry->val_str(&is_var_null, &str, NOT_FIXED_DEC); /* - Save WHERE clause pointers, because they may be changed - during query optimisation. + NULL value of variable checked early as entry->value so here + we can't get NULL in normal conditions */ - sl->prep_where= sl->where; + DBUG_ASSERT(!is_var_null); + if (!var_value) + goto end; + } + else + { /* - Switch off a temporary flag that prevents evaluation of - subqueries in statement prepare. + variable absent or equal to NULL, so we need to set variable to + something reasonable to get a readable error message during parsing */ - sl->uncacheable&= ~UNCACHEABLE_PREPARE; + str.set(STRING_WITH_LEN("NULL"), &my_charset_latin1); } - stmt->state= Item_arena::PREPARED; - } - DBUG_RETURN(!stmt); + needs_conversion= String::needs_conversion(var_value->length(), + var_value->charset(), to_cs, + &unused); + + len= (needs_conversion ? var_value->length() * to_cs->mbmaxlen : + var_value->length()); + if (!(query_str= alloc_root(thd->mem_root, len+1))) + goto end; + + if (needs_conversion) + { + uint dummy_errors; + len= copy_and_convert(query_str, len, to_cs, var_value->ptr(), + var_value->length(), var_value->charset(), + &dummy_errors); + } + else + memcpy(query_str, var_value->ptr(), var_value->length()); + query_str[len]= '\0'; // Safety (mostly for debug) + *query_len= len; + } + else + { + query_str= lex->prepared_stmt_code.str; + *query_len= lex->prepared_stmt_code.length; + } +end: + return query_str; } -/* Reinit statement before execution */ -static void reset_stmt_for_execute(Prepared_statement *stmt) +/* Init PS/SP specific parse tree members. */ + +static void init_stmt_after_parse(LEX *lex) { - THD *thd= stmt->thd; - LEX *lex= stmt->lex; SELECT_LEX *sl= lex->all_selects_list; - + /* + Switch off a temporary flag that prevents evaluation of + subqueries in statement prepare. + */ for (; sl; sl= sl->next_select_in_list()) + sl->uncacheable&= ~UNCACHEABLE_PREPARE; +} + +/* + SQLCOM_PREPARE implementation. + + SYNOPSIS + mysql_sql_stmt_prepare() + thd thread handle + + DESCRIPTION + Prepare an SQL prepared statement. This is called from + mysql_execute_command and should therefore behave like an + ordinary query (e.g. should not reset any global THD data). + + RETURN VALUE + none: in case of success, OK packet is sent to the client, + otherwise an error message is set in THD +*/ + +void mysql_sql_stmt_prepare(THD *thd) +{ + LEX *lex= thd->lex; + LEX_STRING *name= &lex->prepared_stmt_name; + Prepared_statement *stmt; + const char *query; + uint query_len; + DBUG_ENTER("mysql_sql_stmt_prepare"); + DBUG_ASSERT(thd->protocol == &thd->protocol_simple); + + if ((stmt= (Prepared_statement*) thd->stmt_map.find_by_name(name))) { - /* remove option which was put by mysql_explain_union() */ - sl->options&= ~SELECT_DESCRIBE; /* - Copy WHERE clause pointers to avoid damaging they by optimisation + If there is a statement with the same name, remove it. It is ok to + remove old and fail to insert a new one at the same time. */ - if (sl->prep_where) - { - sl->where= sl->prep_where->copy_andor_structure(thd); - sl->where->cleanup(); - } - DBUG_ASSERT(sl->join == 0); - ORDER *order; - /* Fix GROUP list */ - for (order= (ORDER *)sl->group_list.first; order; order= order->next) - order->item= &order->item_ptr; - /* Fix ORDER list */ - for (order= (ORDER *)sl->order_list.first; order; order= order->next) - order->item= &order->item_ptr; + if (stmt->deallocate()) + DBUG_VOID_RETURN; + } - /* - TODO: When the new table structure is ready, then have a status bit - to indicate the table is altered, and re-do the setup_* - and open the tables back. - */ - for (TABLE_LIST *tables= (TABLE_LIST*) sl->table_list.first; - tables; - tables= tables->next) + if (! (query= get_dynamic_sql_string(lex, &query_len)) || + ! (stmt= new Prepared_statement(thd, &thd->protocol_simple))) + { + DBUG_VOID_RETURN; /* out of memory */ + } + + if (stmt->set_name(name) || thd->stmt_map.insert(stmt)) + { + delete stmt; + DBUG_VOID_RETURN; + } + + if (stmt->prepare(query, query_len+1)) + { + /* Statement map deletes the statement on erase */ + thd->stmt_map.erase(stmt); + } + else + send_ok(thd, 0L, 0L, "Statement prepared"); + + DBUG_VOID_RETURN; +} + +/* Reinit prepared statement/stored procedure before execution */ + +void reinit_stmt_before_use(THD *thd, LEX *lex) +{ + SELECT_LEX *sl= lex->all_selects_list; + DBUG_ENTER("reinit_stmt_before_use"); + + /* + We have to update "thd" pointer in LEX, all its units and in LEX::result, + since statements which belong to trigger body are associated with TABLE + object and because of this can be used in different threads. + */ + lex->thd= thd; + + if (lex->empty_field_list_on_rset) + { + lex->empty_field_list_on_rset= 0; + lex->field_list.empty(); + } + for (; sl; sl= sl->next_select_in_list()) + { + if (!sl->first_execution) { + /* remove option which was put by mysql_explain_union() */ + sl->options&= ~SELECT_DESCRIBE; + + /* see unique_table() */ + sl->exclude_from_table_unique_test= FALSE; + /* - Reset old pointers to TABLEs: they are not valid since the tables - were closed in the end of previous prepare or execute call. + Copy WHERE, HAVING clause pointers to avoid damaging them by optimisation */ - tables->table= 0; - tables->table_list= 0; + if (sl->prep_where) + { + sl->where= sl->prep_where->copy_andor_structure(thd); + sl->where->cleanup(); + } + if (sl->prep_having) + { + sl->having= sl->prep_having->copy_andor_structure(thd); + sl->having->cleanup(); + } + DBUG_ASSERT(sl->join == 0); + ORDER *order; + /* Fix GROUP list */ + for (order= (ORDER *)sl->group_list.first; order; order= order->next) + order->item= &order->item_ptr; + /* Fix ORDER list */ + for (order= (ORDER *)sl->order_list.first; order; order= order->next) + order->item= &order->item_ptr; } - { SELECT_LEX_UNIT *unit= sl->master_unit(); unit->unclean(); unit->types.empty(); /* for derived tables & PS (which can't be reset by Item_subquery) */ unit->reinit_exec_mechanism(); + unit->set_thd(thd); + } + } + + /* + TODO: When the new table structure is ready, then have a status bit + to indicate the table is altered, and re-do the setup_* + and open the tables back. + */ + /* + NOTE: We should reset whole table list here including all tables added + by prelocking algorithm (it is not a problem for substatements since + they have their own table list). + */ + for (TABLE_LIST *tables= lex->query_tables; + tables; + tables= tables->next_global) + { + /* + Reset old pointers to TABLEs: they are not valid since the tables + were closed in the end of previous prepare or execute call. + */ + tables->table= 0; + /* Reset is_schema_table_processed value(needed for I_S tables */ + tables->is_schema_table_processed= FALSE; + + if (tables->prep_on_expr) + { + tables->on_expr= tables->prep_on_expr->copy_andor_structure(thd); + tables->on_expr->cleanup(); } } lex->current_select= &lex->select_lex; + + /* restore original list used in INSERT ... SELECT */ + if (lex->leaf_tables_insert) + lex->select_lex.leaf_tables= lex->leaf_tables_insert; + if (lex->result) + { lex->result->cleanup(); + lex->result->set_thd(thd); + } + lex->allow_sum_func= 0; + lex->in_sum_func= NULL; + DBUG_VOID_RETURN; } -/* - Clears parameters from data left from previous execution or long data - +/* + Clears parameters from data left from previous execution or long data + SYNOPSIS reset_stmt_params() - stmt - prepared statement for which parameters should be reset + stmt prepared statement for which parameters should + be reset */ static void reset_stmt_params(Prepared_statement *stmt) @@ -1759,48 +2168,53 @@ 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. + COM_STMT_EXECUTE handler: execute a previously prepared statement. + SYNOPSIS mysql_stmt_execute() - thd Current thread - packet Query string - packet_length Query string length, including terminator character. + thd current thread + packet parameter types and data, if any + packet_length packet length, including the terminator character. + + DESCRIPTION + If there are any parameters, then replace parameter markers with the + data supplied from the client, and then execute the statement. + This function uses binary protocol to send a possible result set + to the client. + + RETURN VALUE + none: in case of success OK packet or a result set is sent to the + client, otherwise an error message is set in THD. */ -void mysql_stmt_execute(THD *thd, char *packet, uint packet_length) +void mysql_stmt_execute(THD *thd, char *packet_arg, uint packet_length) { + uchar *packet= (uchar*)packet_arg; // GCC 4.0.1 workaround ulong stmt_id= uint4korr(packet); - /* - Query text for binary log, or empty string if the query is not put into - binary log. - */ + ulong flags= (ulong) ((uchar) packet[4]); + /* Query text for binary, general or slow log, if any of them is open */ String expanded_query; #ifndef EMBEDDED_LIBRARY uchar *packet_end= (uchar *) packet + packet_length - 1; #endif Prepared_statement *stmt; + bool error; DBUG_ENTER("mysql_stmt_execute"); packet+= 9; /* stmt_id + 5 bytes of flags */ - statistic_increment(com_stmt_execute, &LOCK_status); - if (!(stmt= find_prepared_statement(thd, stmt_id, "mysql_stmt_execute", - SEND_ERROR))) + /* First of all clear possible warnings from the previous command */ + mysql_reset_thd_for_next_command(thd); + + if (!(stmt= find_prepared_statement(thd, stmt_id, "mysql_stmt_execute"))) DBUG_VOID_RETURN; - DBUG_PRINT("exec_query:", ("%s", stmt->query)); + DBUG_PRINT("exec_query", ("%s", stmt->query)); + DBUG_PRINT("info",("stmt: %p", stmt)); - /* Check if we got an error when sending long data */ - if (stmt->state == Item_arena::ERROR) - { - send_error(thd, stmt->last_errno, stmt->last_error); - DBUG_VOID_RETURN; - } + sp_cache_flush_obsolete(&thd->sp_proc_cache); + sp_cache_flush_obsolete(&thd->sp_func_cache); - DBUG_ASSERT(thd->free_list == NULL); - mysql_reset_thd_for_next_command(thd); #ifndef EMBEDDED_LIBRARY if (stmt->param_count) { @@ -1812,138 +2226,145 @@ void mysql_stmt_execute(THD *thd, char *packet, uint packet_length) } #else /* - In embedded library we re-install conversion routines each time - we set params, and also we don't need to parse packet. + In embedded library we re-install conversion routines each time + 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, &expanded_query)) goto set_params_data_err; #endif - thd->protocol= &thd->protocol_prep; // Switch to binary protocol - execute_stmt(thd, stmt, &expanded_query, TRUE); - thd->protocol= &thd->protocol_simple; // Use normal protocol + if (!(specialflag & SPECIAL_NO_PRIOR)) + my_pthread_setprio(pthread_self(),QUERY_PRIOR); + error= stmt->execute(&expanded_query, + test(flags & (ulong) CURSOR_TYPE_READ_ONLY)); + if (!(specialflag & SPECIAL_NO_PRIOR)) + my_pthread_setprio(pthread_self(), WAIT_PRIOR); + if (error == 0) + mysql_log.write(thd, COM_STMT_EXECUTE, "[%lu] %s", stmt->id, thd->query); + DBUG_VOID_RETURN; set_params_data_err: - reset_stmt_params(stmt); my_error(ER_WRONG_ARGUMENTS, MYF(0), "mysql_stmt_execute"); - send_error(thd); + reset_stmt_params(stmt); DBUG_VOID_RETURN; } /* - Execute prepared statement using parameter values from - lex->prepared_stmt_params and send result to the client using text protocol. + SQLCOM_EXECUTE implementation. + + SYNOPSIS + mysql_sql_stmt_execute() + thd thread handle + + DESCRIPTION + Execute prepared statement using parameter values from + lex->prepared_stmt_params and send result to the client using + text protocol. This is called from mysql_execute_command and + therefore should behave like an ordinary query (e.g. not change + global THD data, such as warning count, server status, etc). + This function uses text protocol to send a possible result set. + + RETURN + none: in case of success, OK (or result set) packet is sent to the + client, otherwise an error is set in THD */ -void mysql_sql_stmt_execute(THD *thd, LEX_STRING *stmt_name) +void mysql_sql_stmt_execute(THD *thd) { + LEX *lex= thd->lex; Prepared_statement *stmt; - /* - Query text for binary log, or empty string if the query is not put into - binary log. - */ + LEX_STRING *name= &lex->prepared_stmt_name; + /* Query text for binary, general or slow log, if any of them is open */ String expanded_query; DBUG_ENTER("mysql_sql_stmt_execute"); + DBUG_PRINT("info", ("EXECUTE: %.*s\n", name->length, name->str)); - /* See comment for statistics_increment in mysql_stmt_prepare */ - statistic_increment(com_stmt_execute, &LOCK_status); - if (!(stmt= (Prepared_statement*)thd->stmt_map.find_by_name(stmt_name))) + if (!(stmt= (Prepared_statement*) thd->stmt_map.find_by_name(name))) { - my_error(ER_UNKNOWN_STMT_HANDLER, MYF(0), stmt_name->length, - stmt_name->str, "EXECUTE"); - send_error(thd); + my_error(ER_UNKNOWN_STMT_HANDLER, MYF(0), + name->length, name->str, "EXECUTE"); DBUG_VOID_RETURN; } - if (stmt->param_count != thd->lex->prepared_stmt_params.elements) + if (stmt->param_count != lex->prepared_stmt_params.elements) { my_error(ER_WRONG_ARGUMENTS, MYF(0), "EXECUTE"); - send_error(thd); DBUG_VOID_RETURN; } - DBUG_ASSERT(thd->free_list == NULL); - /* Must go before setting variables, as it clears thd->user_var_events */ - mysql_reset_thd_for_next_command(thd); - thd->set_n_backup_statement(stmt, &thd->stmt_backup); - if (stmt->set_params_from_vars(stmt, - thd->stmt_backup.lex->prepared_stmt_params, + DBUG_PRINT("info",("stmt: %p", stmt)); + + if (stmt->set_params_from_vars(stmt, lex->prepared_stmt_params, &expanded_query)) - { - my_error(ER_WRONG_ARGUMENTS, MYF(0), "EXECUTE"); - send_error(thd); - } - thd->command= COM_EXECUTE; /* For nice messages in general log */ - execute_stmt(thd, stmt, &expanded_query, FALSE); + goto set_params_data_err; + + (void) stmt->execute(&expanded_query, FALSE); + + DBUG_VOID_RETURN; + +set_params_data_err: + my_error(ER_WRONG_ARGUMENTS, MYF(0), "EXECUTE"); + reset_stmt_params(stmt); DBUG_VOID_RETURN; } /* - Execute prepared statement. + COM_STMT_FETCH handler: fetches requested amount of rows from cursor + 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. + mysql_stmt_fetch() + thd Thread handle + packet Packet from client (with stmt_id & num_rows) + packet_length Length of packet */ -static void execute_stmt(THD *thd, Prepared_statement *stmt, - String *expanded_query, bool set_context) +void mysql_stmt_fetch(THD *thd, char *packet, uint packet_length) { - DBUG_ENTER("execute_stmt"); - if (set_context) - thd->set_n_backup_statement(stmt, &thd->stmt_backup); - reset_stmt_for_execute(stmt); + /* assume there is always place for 8-16 bytes */ + ulong stmt_id= uint4korr(packet); + ulong num_rows= uint4korr(packet+4); + Prepared_statement *stmt; + Statement stmt_backup; + Server_side_cursor *cursor; + DBUG_ENTER("mysql_stmt_fetch"); - if (expanded_query->length() && - alloc_query(thd, (char *)expanded_query->ptr(), - expanded_query->length()+1)) + /* First of all clear possible warnings from the previous command */ + mysql_reset_thd_for_next_command(thd); + statistic_increment(thd->status_var.com_stmt_fetch, &LOCK_status); + if (!(stmt= find_prepared_statement(thd, stmt_id, "mysql_stmt_fetch"))) + DBUG_VOID_RETURN; + + cursor= stmt->cursor; + if (!cursor) { - my_error(ER_OUTOFMEMORY, 0, expanded_query->length()); + my_error(ER_STMT_HAS_NO_OPEN_CURSOR, MYF(0), stmt_id); DBUG_VOID_RETURN; } - mysql_log.write(thd, thd->command, "[%lu] %s", stmt->id, thd->query); - /* - At first execution of prepared statement we will perform logical - transformations of the query tree (i.e. negations elimination). - This should be done permanently on the parse tree of this statement. - */ - thd->current_arena= stmt; + + thd->stmt_arena= stmt; + thd->set_n_backup_statement(stmt, &stmt_backup); if (!(specialflag & SPECIAL_NO_PRIOR)) - my_pthread_setprio(pthread_self(),QUERY_PRIOR); - mysql_execute_command(thd); - thd->lex->unit.cleanup(); + my_pthread_setprio(pthread_self(), QUERY_PRIOR); + + cursor->fetch(num_rows); + if (!(specialflag & SPECIAL_NO_PRIOR)) my_pthread_setprio(pthread_self(), WAIT_PRIOR); - /* - 'start_time' is set in dispatch_command, but THD::query will - be freed when we return from this function. So let's log the slow - query here. - */ - log_slow_statement(thd); - /* Prevent from second logging in the end of dispatch_command */ - thd->enable_slow_log= FALSE; - - /* Free Items that were created during this execution of the PS. */ - free_items(thd->free_list); - thd->free_list= 0; - if (stmt->state == Item_arena::PREPARED) - stmt->state= Item_arena::EXECUTED; - thd->current_arena= thd; - cleanup_items(stmt->free_list); - thd->rollback_item_tree_changes(); - reset_stmt_params(stmt); - close_thread_tables(thd); // to close derived tables - thd->set_statement(&thd->stmt_backup); + + if (!cursor->is_open()) + { + stmt->close_cursor(); + thd->cursor= 0; + reset_stmt_params(stmt); + } + + thd->restore_backup_statement(stmt, &stmt_backup); + thd->stmt_arena= thd; + DBUG_VOID_RETURN; } @@ -1952,8 +2373,8 @@ static void execute_stmt(THD *thd, Prepared_statement *stmt, 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. @@ -1961,6 +2382,7 @@ static void execute_stmt(THD *thd, Prepared_statement *stmt, - clear an error happened during mysql_stmt_send_long_data - cancel long data stream for all placeholders without having to call mysql_stmt_execute. + - close an open cursor Sends 'OK' packet in case of success (statement was reset) or 'ERROR' packet (unrecoverable error/statement not found/etc). */ @@ -1970,70 +2392,102 @@ void mysql_stmt_reset(THD *thd, char *packet) /* There is always space for 4 bytes in buffer */ ulong stmt_id= uint4korr(packet); Prepared_statement *stmt; - DBUG_ENTER("mysql_stmt_reset"); - statistic_increment(com_stmt_reset, &LOCK_status); - if (!(stmt= find_prepared_statement(thd, stmt_id, "mysql_stmt_reset", - SEND_ERROR))) + /* First of all clear possible warnings from the previous command */ + mysql_reset_thd_for_next_command(thd); + + statistic_increment(thd->status_var.com_stmt_reset, &LOCK_status); + if (!(stmt= find_prepared_statement(thd, stmt_id, "mysql_stmt_reset"))) DBUG_VOID_RETURN; - stmt->state= Item_arena::PREPARED; + stmt->close_cursor(); - /* - Clear parameters from data which could be set by + /* + Clear parameters from data which could be set by mysql_stmt_send_long_data() call. */ reset_stmt_params(stmt); - mysql_reset_thd_for_next_command(thd); + stmt->state= Query_arena::PREPARED; + send_ok(thd); - + DBUG_VOID_RETURN; } /* Delete a prepared statement from memory. - Note: we don't send any reply to that command. + Note: we don't send any reply to this command. */ -void mysql_stmt_free(THD *thd, char *packet) +void mysql_stmt_close(THD *thd, char *packet) { /* There is always space for 4 bytes in packet buffer */ ulong stmt_id= uint4korr(packet); Prepared_statement *stmt; + DBUG_ENTER("mysql_stmt_close"); - DBUG_ENTER("mysql_stmt_free"); - - statistic_increment(com_stmt_close, &LOCK_status); - if (!(stmt= find_prepared_statement(thd, stmt_id, "mysql_stmt_close", - DONT_SEND_ERROR))) + if (!(stmt= find_prepared_statement(thd, stmt_id, "mysql_stmt_close"))) DBUG_VOID_RETURN; - /* Statement map deletes statement on erase */ - thd->stmt_map.erase(stmt); + /* + The only way currently a statement can be deallocated when it's + in use is from within Dynamic SQL. + */ + DBUG_ASSERT(! (stmt->flags & (uint) Prepared_statement::IS_IN_USE)); + (void) stmt->deallocate(); + DBUG_VOID_RETURN; } /* - Long data in pieces from client + SQLCOM_DEALLOCATE implementation. + + DESCRIPTION + Close an SQL prepared statement. As this can be called from Dynamic + SQL, we should be careful to not close a statement that is currently + being executed. + + RETURN VALUE + none: OK packet is sent in case of success, otherwise an error + message is set in THD +*/ + +void mysql_sql_stmt_close(THD *thd) +{ + Prepared_statement* stmt; + LEX_STRING *name= &thd->lex->prepared_stmt_name; + DBUG_PRINT("info", ("DEALLOCATE PREPARE: %.*s\n", name->length, name->str)); + + if (! (stmt= (Prepared_statement*) thd->stmt_map.find_by_name(name))) + { + my_error(ER_UNKNOWN_STMT_HANDLER, MYF(0), + name->length, name->str, "DEALLOCATE PREPARE"); + return; + } + + if (stmt->deallocate() == 0) + send_ok(thd); +} + +/* + Handle long data in pieces from client. SYNOPSIS mysql_stmt_get_longdata() - thd Thread handle - pos String to append - packet_length Length of string + thd Thread handle + packet String to append + packet_length Length of string (including end \0) DESCRIPTION - Get a part of a long data. - To make the protocol efficient, we are not sending any return packages - here. - If something goes wrong, then we will send the error on 'execute' - - We assume that the client takes care of checking that all parts are sent - to the server. (No checking that we get a 'end of column' in the server) + Get a part of a long data. To make the protocol efficient, we are + not sending any return packets here. If something goes wrong, then + we will send the error on 'execute' We assume that the client takes + care of checking that all parts are sent to the server. (No checking + that we get a 'end of column' in the server is performed). */ void mysql_stmt_get_longdata(THD *thd, char *packet, ulong packet_length) @@ -2043,13 +2497,12 @@ void mysql_stmt_get_longdata(THD *thd, char *packet, ulong packet_length) Prepared_statement *stmt; Item_param *param; char *packet_end= packet + packet_length - 1; - DBUG_ENTER("mysql_stmt_get_longdata"); - statistic_increment(com_stmt_send_long_data, &LOCK_status); + statistic_increment(thd->status_var.com_stmt_send_long_data, &LOCK_status); #ifndef EMBEDDED_LIBRARY /* Minimal size of long data packet is 6 bytes */ - if ((ulong) (packet_end - packet) < MYSQL_LONG_DATA_HEADER) + if (packet_length <= MYSQL_LONG_DATA_HEADER) { my_error(ER_WRONG_ARGUMENTS, MYF(0), "mysql_stmt_send_long_data"); DBUG_VOID_RETURN; @@ -2059,8 +2512,8 @@ void mysql_stmt_get_longdata(THD *thd, char *packet, ulong packet_length) stmt_id= uint4korr(packet); packet+= 4; - if (!(stmt=find_prepared_statement(thd, stmt_id, "mysql_stmt_send_long_data", - DONT_SEND_ERROR))) + if (!(stmt=find_prepared_statement(thd, stmt_id, + "mysql_stmt_send_long_data"))) DBUG_VOID_RETURN; param_number= uint2korr(packet); @@ -2069,7 +2522,7 @@ void mysql_stmt_get_longdata(THD *thd, char *packet, ulong packet_length) if (param_number >= stmt->param_count) { /* Error will be sent in execute call */ - stmt->state= Item_arena::ERROR; + stmt->state= Query_arena::ERROR; stmt->last_errno= ER_WRONG_ARGUMENTS; sprintf(stmt->last_error, ER(ER_WRONG_ARGUMENTS), "mysql_stmt_send_long_data"); @@ -2085,7 +2538,7 @@ void mysql_stmt_get_longdata(THD *thd, char *packet, ulong packet_length) if (param->set_longdata(thd->extra_data, thd->extra_length)) #endif { - stmt->state= Item_arena::ERROR; + stmt->state= Query_arena::ERROR; stmt->last_errno= ER_OUTOFMEMORY; sprintf(stmt->last_error, ER(ER_OUTOFMEMORY), 0); } @@ -2093,12 +2546,70 @@ void mysql_stmt_get_longdata(THD *thd, char *packet, ulong packet_length) } -Prepared_statement::Prepared_statement(THD *thd_arg) - :Statement(thd_arg), +/*************************************************************************** + Select_fetch_protocol_prep +****************************************************************************/ + +Select_fetch_protocol_prep::Select_fetch_protocol_prep(THD *thd) + :protocol(thd) +{} + +bool Select_fetch_protocol_prep::send_fields(List<Item> &list, uint flags) +{ + bool rc; + Protocol *save_protocol= thd->protocol; + + /* + Protocol::send_fields caches the information about column types: + this information is later used to send data. Therefore, the same + dedicated Protocol object must be used for all operations with + a cursor. + */ + thd->protocol= &protocol; + rc= select_send::send_fields(list, flags); + thd->protocol= save_protocol; + + return rc; +} + +bool Select_fetch_protocol_prep::send_eof() +{ + Protocol *save_protocol= thd->protocol; + + thd->protocol= &protocol; + ::send_eof(thd); + thd->protocol= save_protocol; + return FALSE; +} + + +bool +Select_fetch_protocol_prep::send_data(List<Item> &fields) +{ + Protocol *save_protocol= thd->protocol; + bool rc; + + thd->protocol= &protocol; + rc= select_send::send_data(fields); + thd->protocol= save_protocol; + return rc; +} + +/*************************************************************************** + Prepared_statement +****************************************************************************/ + +Prepared_statement::Prepared_statement(THD *thd_arg, Protocol *protocol_arg) + :Statement(INITIALIZED, ++thd_arg->statement_id_counter, + thd_arg->variables.query_alloc_block_size, + thd_arg->variables.query_prealloc_size), thd(thd_arg), + result(thd_arg), + protocol(protocol_arg), param_array(0), param_count(0), - last_errno(0) + last_errno(0), + flags((uint) IS_IN_USE) { *last_error= '\0'; } @@ -2129,15 +2640,311 @@ void Prepared_statement::setup_set_params() } +/* + DESCRIPTION + Destroy this prepared statement, cleaning up all used memory + and resources. This is called from ::deallocate() to + handle COM_STMT_CLOSE and DEALLOCATE PREPARE or when + THD ends and all prepared statements are freed. +*/ + Prepared_statement::~Prepared_statement() { - free_items(free_list); + DBUG_ENTER("Prepared_statement::~Prepared_statement"); + DBUG_PRINT("enter",("stmt: %p cursor: %p", this, cursor)); + delete cursor; + /* + We have to call free on the items even if cleanup is called as some items, + like Item_param, don't free everything until free_items() + */ + free_items(); delete lex->result; + DBUG_VOID_RETURN; } -Item_arena::Type Prepared_statement::type() const +Query_arena::Type Prepared_statement::type() const { return PREPARED_STATEMENT; } + +void Prepared_statement::cleanup_stmt() +{ + DBUG_ENTER("Prepared_statement::cleanup_stmt"); + DBUG_PRINT("enter",("stmt: %p", this)); + + /* The order is important */ + lex->unit.cleanup(); + cleanup_items(free_list); + thd->cleanup_after_query(); + close_thread_tables(thd); + thd->rollback_item_tree_changes(); + + DBUG_VOID_RETURN; +} + + +bool Prepared_statement::set_name(LEX_STRING *name_arg) +{ + name.length= name_arg->length; + name.str= memdup_root(mem_root, (char*) name_arg->str, name_arg->length); + return name.str == 0; +} + +/************************************************************************** + Common parts of mysql_[sql]_stmt_prepare, mysql_[sql]_stmt_execute. + Essentially, these functions do all the magic of preparing/executing + a statement, leaving network communication, input data handling and + global THD state management to the caller. +***************************************************************************/ + +/* + Parse statement text, validate the statement, and prepare it for execution. + + SYNOPSIS + Prepared_statement::prepare() + packet statement text + packet_len + + DESCRIPTION + You should not change global THD state in this function, if at all + possible: it may be called from any context, e.g. when executing + a COM_* command, and SQLCOM_* command, or a stored procedure. + + NOTES + Precondition. + ------------- + The caller must ensure that thd->change_list and thd->free_list + is empty: this function will not back them up but will free + in the end of its execution. + + Postcondition. + -------------- + thd->mem_root contains unused memory allocated during validation. +*/ + +bool Prepared_statement::prepare(const char *packet, uint packet_len) +{ + bool error; + Statement stmt_backup; + Query_arena *old_stmt_arena; + DBUG_ENTER("Prepared_statement::prepare"); + /* + If this is an SQLCOM_PREPARE, we also increase Com_prepare_sql. + However, it seems handy if com_stmt_prepare is increased always, + no matter what kind of prepare is processed. + */ + statistic_increment(thd->status_var.com_stmt_prepare, &LOCK_status); + + /* + alloc_query() uses thd->memroot && thd->query, so we should call + both of backup_statement() and backup_query_arena() here. + */ + thd->set_n_backup_statement(this, &stmt_backup); + thd->set_n_backup_active_arena(this, &stmt_backup); + + if (alloc_query(thd, packet, packet_len)) + { + thd->restore_backup_statement(this, &stmt_backup); + thd->restore_active_arena(this, &stmt_backup); + DBUG_RETURN(TRUE); + } + + old_stmt_arena= thd->stmt_arena; + thd->stmt_arena= this; + lex_start(thd, (uchar*) thd->query, thd->query_length); + lex->safe_to_cache_query= FALSE; + lex->stmt_prepare_mode= TRUE; + + error= MYSQLparse((void *)thd) || thd->is_fatal_error || + thd->net.report_error || init_param_array(this); + /* + While doing context analysis of the query (in check_prepared_statement) + we allocate a lot of additional memory: for open tables, JOINs, derived + tables, etc. Let's save a snapshot of current parse tree to the + statement and restore original THD. In cases when some tree + transformation can be reused on execute, we set again thd->mem_root from + stmt->mem_root (see setup_wild for one place where we do that). + */ + thd->restore_active_arena(this, &stmt_backup); + + /* + If called from a stored procedure, ensure that we won't rollback + external changes when cleaning up after validation. + */ + DBUG_ASSERT(thd->change_list.is_empty()); + /* + If the free_list is not empty, we'll wrongly free some externally + allocated items when cleaning up after validation of the prepared + statement. + */ + DBUG_ASSERT(thd->free_list == NULL); + + if (error == 0) + error= check_prepared_statement(this, name.str != 0); + + if (error && lex->sphead) + { + delete lex->sphead; + lex->sphead= NULL; + } + lex_end(lex); + cleanup_stmt(); + thd->restore_backup_statement(this, &stmt_backup); + thd->stmt_arena= old_stmt_arena; + + if (error == 0) + { + setup_set_params(); + init_stmt_after_parse(lex); + state= Query_arena::PREPARED; + flags&= ~ (uint) IS_IN_USE; + } + DBUG_RETURN(error); +} + +/* + Execute a prepared statement. + + SYNOPSIS + Prepared_statement::execute() + expanded_query A query for binlogging which has all parameter + markers ('?') replaced with their actual values. + open_cursor True if an attempt to open a cursor should be made. + Currenlty used only in the binary protocol. + + DESCRIPTION + You should not change global THD state in this function, if at all + possible: it may be called from any context, e.g. when executing + a COM_* command, and SQLCOM_* command, or a stored procedure. + + NOTES + Preconditions, postconditions. + ------------------------------ + See the comment for Prepared_statement::prepare(). + + RETURN + FALSE ok + TRUE Error +*/ + +bool Prepared_statement::execute(String *expanded_query, bool open_cursor) +{ + Statement stmt_backup; + Query_arena *old_stmt_arena; + Item *old_free_list; + bool error= TRUE; + + statistic_increment(thd->status_var.com_stmt_execute, &LOCK_status); + + /* Check if we got an error when sending long data */ + if (state == Query_arena::ERROR) + { + my_message(last_errno, last_error, MYF(0)); + return TRUE; + } + if (flags & (uint) IS_IN_USE) + { + my_error(ER_PS_NO_RECURSION, MYF(0)); + return TRUE; + } + + /* + For SHOW VARIABLES lex->result is NULL, as it's a non-SELECT + command. For such queries we don't return an error and don't + open a cursor -- the client library will recognize this case and + materialize the result set. + For SELECT statements lex->result is created in + check_prepared_statement. lex->result->simple_select() is FALSE + in INSERT ... SELECT and similar commands. + */ + + if (open_cursor && lex->result && !lex->result->simple_select()) + { + DBUG_PRINT("info",("Cursor asked for not SELECT stmt")); + my_error(ER_SP_BAD_CURSOR_QUERY, MYF(0)); + return TRUE; + } + + /* In case the command has a call to SP which re-uses this statement name */ + flags|= IS_IN_USE; + + close_cursor(); + + /* + If the free_list is not empty, we'll wrongly free some externally + allocated items when cleaning up after execution of this statement. + */ + DBUG_ASSERT(thd->change_list.is_empty()); + DBUG_ASSERT(thd->free_list == NULL); + thd->set_n_backup_statement(this, &stmt_backup); + if (expanded_query->length() && + alloc_query(thd, (char*) expanded_query->ptr(), + expanded_query->length()+1)) + { + my_error(ER_OUTOFMEMORY, 0, expanded_query->length()); + goto error; + } + /* + Expanded query is needed for slow logging, so we want thd->query + to point at it even after we restore from backup. This is ok, as + expanded query was allocated in thd->mem_root. + */ + stmt_backup.query= thd->query; + stmt_backup.query_length= thd->query_length; + + /* + At first execution of prepared statement we may perform logical + transformations of the query tree. Such changes should be performed + on the parse tree of current prepared statement and new items should + be allocated in its memory root. Set the appropriate pointer in THD + to the arena of the statement. + */ + old_stmt_arena= thd->stmt_arena; + thd->stmt_arena= this; + reinit_stmt_before_use(thd, lex); + + thd->protocol= protocol; /* activate stmt protocol */ + error= (open_cursor ? + mysql_open_cursor(thd, (uint) ALWAYS_MATERIALIZED_CURSOR, + &result, &cursor) : + mysql_execute_command(thd)); + thd->protocol= &thd->protocol_simple; /* use normal protocol */ + + /* Assert that if an error, no cursor is open */ + DBUG_ASSERT(! (error && cursor)); + + if (! cursor) + { + cleanup_stmt(); + reset_stmt_params(this); + } + + thd->set_statement(&stmt_backup); + thd->stmt_arena= old_stmt_arena; + + if (state == Query_arena::PREPARED) + state= Query_arena::EXECUTED; + +error: + flags&= ~ (uint) IS_IN_USE; + return error; +} + + +/* Common part of DEALLOCATE PREPARE and mysql_stmt_close */ + +bool Prepared_statement::deallocate() +{ + /* We account deallocate in the same manner as mysql_stmt_close */ + statistic_increment(thd->status_var.com_stmt_close, &LOCK_status); + if (flags & (uint) IS_IN_USE) + { + my_error(ER_PS_NO_RECURSION, MYF(0)); + return TRUE; + } + /* Statement map calls delete stmt on erase */ + thd->stmt_map.erase(this); + return FALSE; +} |