summaryrefslogtreecommitdiff
path: root/sql/sql_prepare.cc
diff options
context:
space:
mode:
Diffstat (limited to 'sql/sql_prepare.cc')
-rw-r--r--sql/sql_prepare.cc2209
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;
+}