summaryrefslogtreecommitdiff
path: root/sql/sql_prepare.cc
diff options
context:
space:
mode:
authorkonstantin@mysql.com <>2005-09-03 03:13:18 +0400
committerkonstantin@mysql.com <>2005-09-03 03:13:18 +0400
commit38486e83c1ee11abb7c52ea6a215960aca0c92a8 (patch)
tree981df38c4f1f46f7080926e10ff505778048f4af /sql/sql_prepare.cc
parenta3ddcdf8fbf2058eeae6d7db7f1dc99785329d7b (diff)
downloadmariadb-git-38486e83c1ee11abb7c52ea6a215960aca0c92a8.tar.gz
Implement WL#2661 "Prepared Statements: Dynamic SQL in Stored Procedures".
The idea of the patch is to separate statement processing logic, such as parsing, validation of the parsed tree, execution and cleanup, from global query processing logic, such as logging, resetting priorities of a thread, resetting stored procedure cache, resetting thread count of errors and warnings. This makes PREPARE and EXECUTE behave similarly to the rest of SQL statements and allows their use in stored procedures. This patch contains a change in behaviour: until recently for each SQL prepared statement command, 2 queries were written to the general log, e.g. [Query] prepare stmt from @stmt_text; [Prepare] select * from t1 <-- contents of @stmt_text The chagne was necessary to prevent [Prepare] commands from being written to the general log when executing a stored procedure with Dynamic SQL. We should consider whether the old behavior is preferrable and probably restore it. This patch refixes Bug#7115, Bug#10975 (partially), Bug#10605 (various bugs in Dynamic SQL reported before it was disabled).
Diffstat (limited to 'sql/sql_prepare.cc')
-rw-r--r--sql/sql_prepare.cc1253
1 files changed, 811 insertions, 442 deletions
diff --git a/sql/sql_prepare.cc b/sql/sql_prepare.cc
index bf0bea804c4..879ea626494 100644
--- a/sql/sql_prepare.cc
+++ b/sql/sql_prepare.cc
@@ -15,9 +15,9 @@
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_STMT_PREPARE';
in the following format:
@@ -25,21 +25,21 @@ Prepare:
- 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.
+ '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:
+When one executes a statement:
- Server gets the command 'COM_STMT_EXECUTE' to execute the
- previously prepared query. If there is any param markers; then client
- will send the data in the following format:
+ 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)]
@@ -48,29 +48,29 @@ Prepare-execute:
[[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.
+ - 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_STMT_SEND_LONG_DATA'.
- The packet recieved will have the format as:
[COM_STMT_SEND_LONG_DATA:1][STMT_ID:4][parameter_number:2][data]
- - data from the packet is appended to long data value buffer for this
+ - 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 "sp_head.h"
#include "sp.h"
#include "sp_cache.h"
@@ -82,7 +82,7 @@ Long data handling:
#endif
/******************************************************************************
- Prepared_statement: statement which can contain placeholders
+ Prepared_statement: a statement that can contain placeholders
******************************************************************************/
class Prepared_statement: public Statement
@@ -93,6 +93,7 @@ public:
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,
@@ -104,15 +105,21 @@ public:
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 Query_arena::Type type() const;
virtual void close_cursor();
-};
+ bool set_name(LEX_STRING *name);
+
+ 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);
+ /* Possible values of flags */
+ static const int IS_IN_USE= 1;
+};
/******************************************************************************
Implementation
@@ -127,13 +134,30 @@ inline bool is_param_null(const uchar *pos, ulong param_no)
enum { STMT_QUERY_LOG_LENGTH= 8192 };
/*
- Seek prepared statement in statement map by id: returns zero if statement
- was not found, pointer otherwise.
+ 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).
+
+ DESCRIPTION
+ Try to find a prepared statement and set THD error if it's not found.
+
+ 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)
{
+ /*
+ 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() != Query_arena::PREPARED_STATEMENT)
@@ -148,7 +172,13 @@ 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
@@ -193,8 +223,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
@@ -239,19 +281,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
@@ -594,8 +638,35 @@ 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,
@@ -707,6 +778,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;
@@ -791,7 +873,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
@@ -829,12 +912,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,
@@ -845,12 +930,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);
@@ -858,7 +944,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))
@@ -895,16 +982,16 @@ 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 global/local table list
+ stmt prepared statement
+ tables global/local table list
RETURN VALUE
- FALSE success
- TRUE error, error message is set in THD
+ FALSE success
+ TRUE error, error message is set in THD
*/
static bool mysql_test_insert(Prepared_statement *stmt,
@@ -988,13 +1075,13 @@ 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, error message is set in THD
- 2 convert to multi_update
+ 0 success
+ 1 error, error message is set in THD
+ 2 convert to multi_update
*/
static int mysql_test_update(Prepared_statement *stmt,
@@ -1068,16 +1155,16 @@ error:
/*
- 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
- FALSE success
- TRUE error, error message is set in THD
+ FALSE success
+ TRUE error, error message is set in THD
*/
static bool mysql_test_delete(Prepared_statement *stmt,
@@ -1106,21 +1193,24 @@ error:
/*
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
- FALSE success
- TRUE error, sent to client
+ 0 success
+ 1 error, error message is set in THD
+ 2 success, and statement metadata has been sent
*/
-static bool mysql_test_select(Prepared_statement *stmt,
- TABLE_LIST *tables, bool text_protocol)
+static int mysql_test_select(Prepared_statement *stmt,
+ TABLE_LIST *tables, bool text_protocol)
{
THD *thd= stmt->thd;
LEX *lex= stmt->lex;
@@ -1158,51 +1248,43 @@ static bool mysql_test_select(Prepared_statement *stmt,
*/
if (unit->prepare(thd, 0, 0, ""))
goto error;
- if (!text_protocol)
+ if (!lex->describe && !text_protocol)
{
- if (lex->describe)
- {
- if (send_prep_stmt(stmt, 0) || thd->protocol->flush())
- goto error;
- }
- 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 error;
+ /* 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, Protocol::SEND_EOF) ||
- thd->protocol->flush())
- 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, Protocol::SEND_EOF) ||
+ thd->protocol->flush())
+ goto error;
+ DBUG_RETURN(2);
}
- DBUG_RETURN(FALSE);
+ DBUG_RETURN(0);
error:
- DBUG_RETURN(TRUE);
+ 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
- FALSE success
- TRUE error, error message is set in THD
+ FALSE success
+ TRUE error, error message is set in THD
*/
static bool mysql_test_do_fields(Prepared_statement *stmt,
@@ -1226,13 +1308,13 @@ static bool 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
- FALSE success
- TRUE error
+ FALSE success
+ TRUE error, error message is set in THD
*/
static bool mysql_test_set_fields(Prepared_statement *stmt,
@@ -1264,9 +1346,9 @@ error:
SYNOPSIS
select_like_stmt_test()
- stmt - prepared statement handler
- specific_prepare - function of command specific prepare
- setup_tables_done_option - options to be passed to LEX::unit.prepare()
+ 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
@@ -1275,8 +1357,8 @@ error:
"specific_prepare" call (like this happens in case of multi-update).
RETURN VALUE
- FALSE success
- TRUE error, error message is set in THD
+ FALSE success
+ TRUE error, error message is set in THD
*/
static bool select_like_stmt_test(Prepared_statement *stmt,
@@ -1300,19 +1382,19 @@ static bool select_like_stmt_test(Prepared_statement *stmt,
/*
Check internal SELECT of the prepared command (with opening and
- locking tables used).
+ locking of used tables).
SYNOPSIS
select_like_stmt_test_with_open_n_lock()
- stmt - prepared statement handler
- 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()
+ 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
+ FALSE success
+ TRUE error
*/
static bool
@@ -1341,12 +1423,12 @@ select_like_stmt_test_with_open_n_lock(Prepared_statement *stmt,
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
- FALSE success
- TRUE error, error message is set in THD
+ FALSE success
+ TRUE error, error message is set in THD
*/
static bool mysql_test_create_table(Prepared_statement *stmt)
@@ -1377,17 +1459,17 @@ static bool mysql_test_create_table(Prepared_statement *stmt)
/*
- 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
- converted converted to multi-update from usual update
+ stmt prepared statement
+ tables list of tables used in this query
+ converted converted to multi-update from usual update
RETURN VALUE
- FALSE success
- TRUE error
+ FALSE success
+ TRUE error, error message is set in THD
*/
static bool mysql_test_multiupdate(Prepared_statement *stmt,
@@ -1404,16 +1486,16 @@ static bool mysql_test_multiupdate(Prepared_statement *stmt,
/*
- 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, error message in THD is set.
+ FALSE success
+ TRUE error, error message in THD is set.
*/
static bool mysql_test_multidelete(Prepared_statement *stmt,
@@ -1449,10 +1531,11 @@ error:
SYNOPSIS
mysql_insert_select_prepare_tester()
- thd thread handler
+ thd thread handle
- NOTE: we need remove first local tables after open_and_lock_tables,
- because mysql_handle_derived use local tables lists
+ NOTE
+ We need to remove the first local table after open_and_lock_tables,
+ because mysql_handle_derived uses local tables lists.
*/
static bool mysql_insert_select_prepare_tester(THD *thd)
@@ -1476,21 +1559,20 @@ static bool mysql_insert_select_prepare_tester(THD *thd)
/*
- Validate and prepare for execution INSERT ... SELECT statement
+ Validate and prepare for execution INSERT ... SELECT statement.
SYNOPSIS
mysql_test_insert_select()
- stmt prepared statemen handler
- tables list of tables of query
+ 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_insert_select(Prepared_statement *stmt,
- TABLE_LIST *tables)
+static bool mysql_test_insert_select(Prepared_statement *stmt,
+ TABLE_LIST *tables)
{
int res;
LEX *lex= stmt->lex;
@@ -1525,7 +1607,7 @@ static int mysql_test_insert_select(Prepared_statement *stmt,
SYNOPSIS
check_prepared_statement()
- stmt prepared statement
+ stmt prepared statement
DESCRIPTION
This function
@@ -1534,8 +1616,8 @@ static int mysql_test_insert_select(Prepared_statement *stmt,
by calling fix_fields.
RETURN VALUE
- FALSE success, statement metadata is sent to client
- TRUE error, error message is set (but not sent)
+ FALSE success, statement metadata is sent to client
+ TRUE error, error message is set in THD (but not sent)
*/
static bool check_prepared_statement(Prepared_statement *stmt,
@@ -1582,11 +1664,13 @@ static bool check_prepared_statement(Prepared_statement *stmt,
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(FALSE);
-
+ 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);
break;
@@ -1695,21 +1779,19 @@ static void cleanup_stmt_and_thd_after_use(Statement *stmt, THD *thd)
DBUG_VOID_RETURN;
}
+
/*
- 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
- FALSE OK, statement prepared successfully
- TRUE 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
@@ -1722,124 +1804,147 @@ static void cleanup_stmt_and_thd_after_use(Statement *stmt, THD *thd)
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.
*/
-bool 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;
- Statement stmt_backup;
- Prepared_statement *stmt= new Prepared_statement(thd);
- bool error;
+ Prepared_statement *stmt= new Prepared_statement(thd, &thd->protocol_prep);
+ bool rc;
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(thd->status_var.com_stmt_prepare, &LOCK_status);
-
if (stmt == 0)
- DBUG_RETURN(TRUE);
-
- if (name)
- {
- stmt->name.length= name->length;
- if (!(stmt->name.str= memdup_root(stmt->mem_root, (char*)name->str,
- name->length)))
- {
- delete stmt;
- DBUG_RETURN(TRUE);
- }
- }
+ DBUG_VOID_RETURN; /* out of memory: error is set in Sql_alloc */
if (thd->stmt_map.insert(stmt))
{
delete stmt;
- DBUG_RETURN(TRUE);
+ DBUG_VOID_RETURN; /* out of memory */
}
- /*
- alloc_query() uses thd->memroot && thd->query, so we have to call
- both of backup_statement() and backup_item_area() here.
- */
- thd->set_n_backup_statement(stmt, &stmt_backup);
- thd->set_n_backup_active_arena(stmt, &stmt_backup);
-
- if (alloc_query(thd, packet, packet_length))
- {
- thd->restore_backup_statement(stmt, &stmt_backup);
- thd->restore_active_arena(stmt, &stmt_backup);
- /* Statement map deletes statement on erase */
- thd->stmt_map.erase(stmt);
- DBUG_RETURN(TRUE);
- }
-
- mysql_log.write(thd, thd->command, "[%lu] %s", stmt->id, packet);
-
- thd->stmt_arena= stmt;
- mysql_init_query(thd, (uchar *) thd->query, thd->query_length);
+ mysql_reset_thd_for_next_command(thd);
/* Reset warnings from previous command */
mysql_reset_errors(thd, 0);
- lex= thd->lex;
- lex->safe_to_cache_query= 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 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(stmt, &stmt_backup);
+ if (!(specialflag & SPECIAL_NO_PRIOR))
+ my_pthread_setprio(pthread_self(),QUERY_PRIOR);
- if (!error)
- error= check_prepared_statement(stmt, test(name));
+ rc= 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);
- if (error && thd->lex->sphead)
- {
- delete thd->lex->sphead;
- thd->lex->sphead= NULL;
- }
- lex_end(lex);
- close_thread_tables(thd);
- cleanup_stmt_and_thd_after_use(stmt, thd);
- thd->restore_backup_statement(stmt, &stmt_backup);
- thd->stmt_arena= thd;
- if (error)
+ if (rc)
{
/* Statement map deletes statement on erase */
thd->stmt_map.erase(stmt);
- stmt= NULL;
+ }
+ 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)
+ {
+ /* 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 *pstr= &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;
+ pstr= entry->val_str(&is_var_null, &str, NOT_FIXED_DEC);
+ /*
+ NULL value of variable checked early as entry->value so here
+ we can't get NULL in normal conditions
+ */
+ DBUG_ASSERT(!is_var_null);
+ if (!pstr)
+ goto end;
+ }
+ else
+ {
+ /*
+ variable absent or equal to NULL, so we need to set variable to
+ something reasonable to get a readable error message during parsing
+ */
+ str.set("NULL", 4, &my_charset_latin1);
+ }
+
+ needs_conversion= String::needs_conversion(pstr->length(),
+ pstr->charset(), to_cs, &unused);
+
+ len= needs_conversion ? pstr->length() * to_cs->mbmaxlen : pstr->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, pstr->ptr(), pstr->length(),
+ pstr->charset(), &dummy_errors);
+ }
+ else
+ memcpy(query_str, pstr->ptr(), pstr->length());
+ query_str[len]= '\0';
+ *query_len= len;
}
else
{
- stmt->setup_set_params();
- init_stmt_after_parse(thd, stmt->lex);
- stmt->state= Query_arena::PREPARED;
+ query_str= lex->prepared_stmt_code.str;
+ *query_len= lex->prepared_stmt_code.length;
}
- DBUG_RETURN(!stmt);
+end:
+ return query_str;
}
-/*
- Init PS/SP specific parse tree members.
-*/
+/* Init PS/SP specific parse tree members. */
-void init_stmt_after_parse(THD *thd, LEX *lex)
+static void init_stmt_after_parse(LEX *lex)
{
SELECT_LEX *sl= lex->all_selects_list;
/*
@@ -1850,6 +1955,66 @@ void init_stmt_after_parse(THD *thd, LEX *lex)
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)))
+ {
+ /*
+ 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 (stmt->deallocate())
+ DBUG_VOID_RETURN;
+ }
+
+ 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 */
@@ -1956,7 +2121,8 @@ void reinit_stmt_before_use(THD *thd, LEX *lex)
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)
@@ -1969,81 +2135,50 @@ 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)
{
ulong stmt_id= uint4korr(packet);
ulong flags= (ulong) ((uchar) packet[4]);
- Statement stmt_backup;
- Cursor *cursor;
- /*
- Query text for binary log, or empty string if the query is not put into
- binary log.
- */
+ /* 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 rc;
DBUG_ENTER("mysql_stmt_execute");
packet+= 9; /* stmt_id + 5 bytes of flags */
- statistic_increment(thd->status_var.com_stmt_execute, &LOCK_status);
if (!(stmt= find_prepared_statement(thd, stmt_id, "mysql_stmt_execute")))
DBUG_VOID_RETURN;
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 == Query_arena::ERROR)
- {
- my_message(stmt->last_errno, stmt->last_error, MYF(0));
- DBUG_VOID_RETURN;
- }
-
- cursor= stmt->cursor;
- if (cursor && cursor->is_open())
- stmt->close_cursor();
-
- DBUG_ASSERT(thd->free_list == NULL);
mysql_reset_thd_for_next_command(thd);
- if (flags & (ulong) CURSOR_TYPE_READ_ONLY)
- {
- if (!stmt->lex->result || !stmt->lex->result->simple_select())
- {
- DBUG_PRINT("info",("Cursor asked for not SELECT stmt"));
- /*
- If lex->result is set in the parser, this is not a SELECT
- statement: we can't open a cursor for it.
- */
- flags= 0;
- my_error(ER_SP_BAD_CURSOR_QUERY, MYF(0));
- goto err;
- }
- else
- {
- DBUG_PRINT("info",("Using READ_ONLY cursor"));
- if (!cursor &&
- !(cursor= stmt->cursor= new (stmt->mem_root) Cursor(thd)))
- DBUG_VOID_RETURN;
- /* If lex->result is set, mysql_execute_command will use it */
- stmt->lex->result= &cursor->result;
- stmt->protocol= &cursor->protocol;
- thd->lock_id= &cursor->lock_id;
- }
- }
+ sp_cache_flush_obsolete(&thd->sp_proc_cache);
+ sp_cache_flush_obsolete(&thd->sp_func_cache);
+
#ifndef EMBEDDED_LIBRARY
if (stmt->param_count)
{
@@ -2062,93 +2197,67 @@ void mysql_stmt_execute(THD *thd, char *packet, uint packet_length)
if (stmt->param_count && stmt->set_params_data(stmt, &expanded_query))
goto set_params_data_err;
#endif
- thd->set_n_backup_statement(stmt, &stmt_backup);
- thd->stmt_arena= stmt;
- reinit_stmt_before_use(thd, stmt->lex);
- /* From now cursors assume that thd->mem_root is clean */
- if (expanded_query.length() &&
- alloc_query(thd, (char *)expanded_query.ptr(),
- expanded_query.length()+1))
- {
- my_error(ER_OUTOFMEMORY, 0, expanded_query.length());
- goto err;
- }
- mysql_log.write(thd, thd->command, "[%lu] %s", stmt->id, thd->query);
-
- thd->protocol= stmt->protocol; // Switch to binary protocol
if (!(specialflag & SPECIAL_NO_PRIOR))
my_pthread_setprio(pthread_self(),QUERY_PRIOR);
- sp_cache_flush_obsolete(&thd->sp_proc_cache);
- sp_cache_flush_obsolete(&thd->sp_func_cache);
- mysql_execute_command(thd);
+ rc= stmt->execute(&expanded_query,
+ test(flags & (ulong) CURSOR_TYPE_READ_ONLY));
if (!(specialflag & SPECIAL_NO_PRIOR))
my_pthread_setprio(pthread_self(), WAIT_PRIOR);
- thd->protocol= &thd->protocol_simple; // Use normal protocol
-
- if (cursor && cursor->is_open())
- {
- /*
- It's safer if we grab THD state after mysql_execute_command is
- finished and not in Cursor::open(), because currently the call to
- Cursor::open is buried deep in JOIN::exec of the top level join.
- */
- cursor->init_from_thd(thd);
-
- if (cursor->close_at_commit)
- thd->stmt_map.add_transient_cursor(stmt);
- }
- else
- {
- close_thread_tables(thd);
- cleanup_stmt_and_thd_after_use(stmt, thd);
- reset_stmt_params(stmt);
- }
+ if (rc)
+ goto err;
- log_slow_statement(thd);
- /* Prevent from second logging in the end of dispatch_command */
- thd->enable_slow_log= FALSE;
+ mysql_log.write(thd, COM_STMT_EXECUTE, "[%lu] %s", stmt->id, thd->query);
- thd->set_statement(&stmt_backup);
- thd->lock_id= &thd->main_lock_id;
- thd->stmt_arena= thd;
DBUG_VOID_RETURN;
set_params_data_err:
- reset_stmt_params(stmt);
my_error(ER_WRONG_ARGUMENTS, MYF(0), "mysql_stmt_execute");
err:
+ 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;
- Statement stmt_backup;
+
DBUG_ENTER("mysql_sql_stmt_execute");
- DBUG_ASSERT(thd->free_list == NULL);
- /* See comment for statistic_increment in mysql_stmt_prepare */
- statistic_increment(thd->status_var.com_stmt_execute, &LOCK_status);
+ DBUG_PRINT("info", ("EXECUTE: %.*s\n", name->length, name->str));
- 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");
+ 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");
DBUG_VOID_RETURN;
@@ -2156,78 +2265,15 @@ void mysql_sql_stmt_execute(THD *thd, LEX_STRING *stmt_name)
DBUG_PRINT("info",("stmt: %p", stmt));
- /* 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, &stmt_backup);
- if (stmt->set_params_from_vars(stmt,
- stmt_backup.lex->prepared_stmt_params,
+ if (stmt->set_params_from_vars(stmt, lex->prepared_stmt_params,
&expanded_query))
{
my_error(ER_WRONG_ARGUMENTS, MYF(0), "EXECUTE");
- }
- thd->command= COM_STMT_EXECUTE; /* For nice messages in general log */
- execute_stmt(thd, stmt, &expanded_query);
- thd->set_statement(&stmt_backup);
- DBUG_VOID_RETURN;
-}
-
-
-/*
- Execute prepared statement.
- SYNOPSIS
- execute_stmt()
- thd Current thread
- stmt Statement to execute
- expanded_query If binary log is enabled, query string with parameter
- placeholders replaced with actual values. Otherwise empty
- string.
- NOTES
- Caller must set parameter values and thd::protocol.
-*/
-
-static void execute_stmt(THD *thd, Prepared_statement *stmt,
- String *expanded_query)
-{
- DBUG_ENTER("execute_stmt");
-
- reinit_stmt_before_use(thd, stmt->lex);
-
- if (expanded_query->length() &&
- alloc_query(thd, (char *)expanded_query->ptr(),
- expanded_query->length()+1))
- {
- my_error(ER_OUTOFMEMORY, MYF(0), expanded_query->length());
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->stmt_arena= stmt;
- if (!(specialflag & SPECIAL_NO_PRIOR))
- my_pthread_setprio(pthread_self(),QUERY_PRIOR);
- mysql_execute_command(thd);
- if (!(specialflag & SPECIAL_NO_PRIOR))
- my_pthread_setprio(pthread_self(), WAIT_PRIOR);
- /*
- '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;
-
- close_thread_tables(thd); // to close derived tables
- cleanup_stmt_and_thd_after_use(stmt, thd);
- reset_stmt_params(stmt);
- thd->stmt_arena= thd;
+ (void) stmt->execute(&expanded_query, FALSE);
- if (stmt->state == Query_arena::PREPARED)
- stmt->state= Query_arena::EXECUTED;
DBUG_VOID_RETURN;
}
@@ -2237,9 +2283,9 @@ static void execute_stmt(THD *thd, Prepared_statement *stmt,
SYNOPSIS
mysql_stmt_fetch()
- thd Thread handler
- packet Packet from client (with stmt_id & num_rows)
- packet_length Length of packet
+ thd Thread handle
+ packet Packet from client (with stmt_id & num_rows)
+ packet_length Length of packet
*/
void mysql_stmt_fetch(THD *thd, char *packet, uint packet_length)
@@ -2301,8 +2347,8 @@ void mysql_stmt_fetch(THD *thd, char *packet, uint packet_length)
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.
@@ -2310,6 +2356,7 @@ void mysql_stmt_fetch(THD *thd, char *packet, uint packet_length)
- 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).
*/
@@ -2339,7 +2386,7 @@ void mysql_stmt_reset(THD *thd, char *packet)
/*
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_close(THD *thd, char *packet)
@@ -2350,33 +2397,65 @@ void mysql_stmt_close(THD *thd, char *packet)
DBUG_ENTER("mysql_stmt_close");
- statistic_increment(thd->status_var.com_stmt_close, &LOCK_status);
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 & 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
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)
@@ -2436,15 +2515,16 @@ void mysql_stmt_get_longdata(THD *thd, char *packet, ulong packet_length)
}
-Prepared_statement::Prepared_statement(THD *thd_arg)
+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),
- protocol(&thd_arg->protocol_prep),
+ protocol(protocol_arg),
param_array(0),
param_count(0),
- last_errno(0)
+ last_errno(0),
+ flags(IS_IN_USE)
{
*last_error= '\0';
}
@@ -2475,6 +2555,14 @@ 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()
{
DBUG_ENTER("Prepared_statement::~Prepared_statement");
@@ -2527,3 +2615,284 @@ void Prepared_statement::close_cursor()
reset_stmt_params(this);
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 rc;
+ 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;
+
+ rc= yyparse((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 (rc == 0)
+ rc= check_prepared_statement(this, name.str != 0);
+
+ if (rc && thd->lex->sphead)
+ {
+ delete thd->lex->sphead;
+ thd->lex->sphead= NULL;
+ }
+ lex_end(lex);
+ close_thread_tables(thd);
+ cleanup_stmt_and_thd_after_use(this, thd);
+ thd->restore_backup_statement(this, &stmt_backup);
+ thd->stmt_arena= old_stmt_arena;
+
+ if (rc == 0)
+ {
+ setup_set_params();
+ init_stmt_after_parse(lex);
+ state= Query_arena::PREPARED;
+ flags&= ~IS_IN_USE;
+ }
+ DBUG_RETURN(rc);
+}
+
+/*
+ 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().
+*/
+
+bool Prepared_statement::execute(String *expanded_query, bool open_cursor)
+{
+ Statement stmt_backup;
+ Query_arena *old_stmt_arena;
+ Item *old_free_list;
+ bool rc= 1;
+
+ 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 1;
+ }
+ if (flags & IS_IN_USE)
+ {
+ my_error(ER_PS_NO_RECURSION, MYF(0));
+ return 1;
+ }
+ /* In case the command has a call to SP which re-uses this statement name */
+ flags|= IS_IN_USE;
+
+ if (cursor && cursor->is_open())
+ 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);
+ if (open_cursor)
+ {
+ if (!lex->result || !lex->result->simple_select())
+ {
+ DBUG_PRINT("info",("Cursor asked for not SELECT stmt"));
+ /*
+ If lex->result is set in the parser, this is not a SELECT
+ statement: we can't open a cursor for it.
+ */
+ my_error(ER_SP_BAD_CURSOR_QUERY, MYF(0));
+ goto error;
+ }
+
+ DBUG_PRINT("info",("Using READ_ONLY cursor"));
+ if (!cursor && !(cursor= new (mem_root) Cursor(thd)))
+ goto error;
+ /* If lex->result is set, mysql_execute_command will use it */
+ lex->result= &cursor->result;
+ protocol= &cursor->protocol;
+ thd->lock_id= &cursor->lock_id;
+ /*
+ Currently cursors can be used only from C API, so
+ we don't have to create an own memory root for them:
+ the one in THD is clean and can be used.
+ */
+ }
+ 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 */
+ mysql_execute_command(thd);
+ thd->protocol= &thd->protocol_simple; /* use normal protocol */
+
+ if (cursor && cursor->is_open())
+ {
+ /*
+ It's safer if we grab THD state after mysql_execute_command is
+ finished and not in Cursor::open(), because currently the call to
+ Cursor::open is buried deep in JOIN::exec of the top level join.
+ */
+ cursor->init_from_thd(thd);
+
+ if (cursor->close_at_commit)
+ thd->stmt_map.add_transient_cursor(this);
+ }
+ else
+ {
+ close_thread_tables(thd);
+ cleanup_stmt_and_thd_after_use(this, thd);
+ reset_stmt_params(this);
+ }
+
+ thd->set_statement(&stmt_backup);
+ thd->lock_id= &thd->main_lock_id;
+ thd->stmt_arena= old_stmt_arena;
+
+ if (state == Query_arena::PREPARED)
+ state= Query_arena::EXECUTED;
+
+ rc= 0;
+error:
+ thd->lock_id= &thd->main_lock_id;
+ flags&= ~IS_IN_USE;
+ return rc;
+}
+
+
+/* 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 & 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;
+}