mirror of
https://github.com/MariaDB/server.git
synced 2025-08-08 11:22:35 +03:00
Merged 5.1 with maria 5.1
This commit is contained in:
@@ -155,21 +155,30 @@ public:
|
||||
virtual void cleanup_stmt();
|
||||
bool set_name(LEX_STRING *name);
|
||||
inline void close_cursor() { delete cursor; cursor= 0; }
|
||||
|
||||
inline bool is_in_use() { return flags & (uint) IS_IN_USE; }
|
||||
inline bool is_protocol_text() const { return protocol == &thd->protocol_text; }
|
||||
bool prepare(const char *packet, uint packet_length);
|
||||
bool execute(String *expanded_query, bool open_cursor);
|
||||
bool execute_loop(String *expanded_query,
|
||||
bool open_cursor,
|
||||
uchar *packet_arg, uchar *packet_end_arg);
|
||||
/* Destroy this statement */
|
||||
bool deallocate();
|
||||
void deallocate();
|
||||
private:
|
||||
/**
|
||||
Store the parsed tree of a prepared statement here.
|
||||
*/
|
||||
LEX main_lex;
|
||||
/**
|
||||
The memory root to allocate parsed tree elements (instances of Item,
|
||||
SELECT_LEX and other classes).
|
||||
*/
|
||||
MEM_ROOT main_mem_root;
|
||||
/* Version of the stored functions cache at the time of prepare. */
|
||||
ulong m_sp_cache_version;
|
||||
private:
|
||||
bool set_db(const char *db, uint db_length);
|
||||
bool set_parameters(String *expanded_query,
|
||||
uchar *packet, uchar *packet_end);
|
||||
bool execute(String *expanded_query, bool open_cursor);
|
||||
bool reprepare();
|
||||
bool validate_metadata(Prepared_statement *copy);
|
||||
void swap_prepared_statement(Prepared_statement *copy);
|
||||
};
|
||||
|
||||
|
||||
@@ -198,7 +207,7 @@ inline bool is_param_null(const uchar *pos, ulong param_no)
|
||||
*/
|
||||
|
||||
static Prepared_statement *
|
||||
find_prepared_statement(THD *thd, ulong id, const char *where)
|
||||
find_prepared_statement(THD *thd, ulong id)
|
||||
{
|
||||
/*
|
||||
To strictly separate namespaces of SQL prepared statements and C API
|
||||
@@ -208,12 +217,8 @@ find_prepared_statement(THD *thd, ulong id, const char *where)
|
||||
Statement *stmt= thd->stmt_map.find(id);
|
||||
|
||||
if (stmt == 0 || stmt->type() != Query_arena::PREPARED_STATEMENT)
|
||||
{
|
||||
char llbuf[22];
|
||||
my_error(ER_UNKNOWN_STMT_HANDLER, MYF(0), sizeof(llbuf), llstr(id, llbuf),
|
||||
where);
|
||||
return 0;
|
||||
}
|
||||
return NULL;
|
||||
|
||||
return (Prepared_statement *) stmt;
|
||||
}
|
||||
|
||||
@@ -945,6 +950,55 @@ static bool emb_insert_params_with_log(Prepared_statement *stmt,
|
||||
|
||||
#endif /*!EMBEDDED_LIBRARY*/
|
||||
|
||||
/**
|
||||
Setup data conversion routines using an array of parameter
|
||||
markers from the original prepared statement.
|
||||
Swap the parameter data of the original prepared
|
||||
statement to the new one.
|
||||
|
||||
Used only when we re-prepare a prepared statement.
|
||||
There are two reasons for this function to exist:
|
||||
|
||||
1) In the binary client/server protocol, parameter metadata
|
||||
is sent only at first execute. Consequently, if we need to
|
||||
reprepare a prepared statement at a subsequent execution,
|
||||
we may not have metadata information in the packet.
|
||||
In that case we use the parameter array of the original
|
||||
prepared statement to setup parameter types of the new
|
||||
prepared statement.
|
||||
|
||||
2) In the binary client/server protocol, we may supply
|
||||
long data in pieces. When the last piece is supplied,
|
||||
we assemble the pieces and convert them from client
|
||||
character set to the connection character set. After
|
||||
that the parameter value is only available inside
|
||||
the parameter, the original pieces are lost, and thus
|
||||
we can only assign the corresponding parameter of the
|
||||
reprepared statement from the original value.
|
||||
|
||||
@param[out] param_array_dst parameter markers of the new statement
|
||||
@param[in] param_array_src parameter markers of the original
|
||||
statement
|
||||
@param[in] param_count total number of parameters. Is the
|
||||
same in src and dst arrays, since
|
||||
the statement query is the same
|
||||
|
||||
@return this function never fails
|
||||
*/
|
||||
|
||||
static void
|
||||
swap_parameter_array(Item_param **param_array_dst,
|
||||
Item_param **param_array_src,
|
||||
uint param_count)
|
||||
{
|
||||
Item_param **dst= param_array_dst;
|
||||
Item_param **src= param_array_src;
|
||||
Item_param **end= param_array_dst + param_count;
|
||||
|
||||
for (; dst < end; ++src, ++dst)
|
||||
(*dst)->set_param_type_and_swap_value(*src);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
Assign prepared statement parameters from user variables.
|
||||
@@ -1104,9 +1158,9 @@ static bool mysql_test_insert(Prepared_statement *stmt,
|
||||
if (table_list->lock_type == TL_WRITE_DELAYED &&
|
||||
!(table_list->table->file->ha_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));
|
||||
my_error(ER_DELAYED_NOT_SUPPORTED, MYF(0), (table_list->view ?
|
||||
table_list->view_name.str :
|
||||
table_list->table_name));
|
||||
goto error;
|
||||
}
|
||||
while ((values= its++))
|
||||
@@ -1270,7 +1324,7 @@ error:
|
||||
*/
|
||||
|
||||
static int mysql_test_select(Prepared_statement *stmt,
|
||||
TABLE_LIST *tables, bool text_protocol)
|
||||
TABLE_LIST *tables)
|
||||
{
|
||||
THD *thd= stmt->thd;
|
||||
LEX *lex= stmt->lex;
|
||||
@@ -1306,7 +1360,7 @@ static int mysql_test_select(Prepared_statement *stmt,
|
||||
*/
|
||||
if (unit->prepare(thd, 0, 0))
|
||||
goto error;
|
||||
if (!lex->describe && !text_protocol)
|
||||
if (!lex->describe && !stmt->is_protocol_text())
|
||||
{
|
||||
/* Make copy of item list, as change_columns may change it */
|
||||
List<Item> fields(lex->select_lex.item_list);
|
||||
@@ -1397,6 +1451,43 @@ error:
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
Validate and prepare for execution CALL statement expressions.
|
||||
|
||||
@param stmt prepared statement
|
||||
@param tables list of tables used in this query
|
||||
@param value_list list of expressions
|
||||
|
||||
@retval FALSE success
|
||||
@retval TRUE error, error message is set in THD
|
||||
*/
|
||||
|
||||
static bool mysql_test_call_fields(Prepared_statement *stmt,
|
||||
TABLE_LIST *tables,
|
||||
List<Item> *value_list)
|
||||
{
|
||||
DBUG_ENTER("mysql_test_call_fields");
|
||||
|
||||
List_iterator<Item> it(*value_list);
|
||||
THD *thd= stmt->thd;
|
||||
Item *item;
|
||||
|
||||
if (tables && check_table_access(thd, SELECT_ACL, tables, UINT_MAX, FALSE) ||
|
||||
open_normal_and_derived_tables(thd, tables, 0))
|
||||
goto err;
|
||||
|
||||
while ((item= it++))
|
||||
{
|
||||
if (!item->fixed && item->fix_fields(thd, it.ref()) ||
|
||||
item->check_cols(1))
|
||||
goto err;
|
||||
}
|
||||
DBUG_RETURN(FALSE);
|
||||
err:
|
||||
DBUG_RETURN(TRUE);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
Check internal SELECT of the prepared command.
|
||||
|
||||
@@ -1518,6 +1609,17 @@ static bool mysql_test_create_table(Prepared_statement *stmt)
|
||||
|
||||
res= select_like_stmt_test(stmt, 0, 0);
|
||||
}
|
||||
else if (lex->create_info.options & HA_LEX_CREATE_TABLE_LIKE)
|
||||
{
|
||||
/*
|
||||
Check that the source table exist, and also record
|
||||
its metadata version. Even though not strictly necessary,
|
||||
we validate metadata of all CREATE TABLE statements,
|
||||
which keeps metadata validation code simple.
|
||||
*/
|
||||
if (open_normal_and_derived_tables(stmt->thd, lex->query_tables, 0))
|
||||
DBUG_RETURN(TRUE);
|
||||
}
|
||||
|
||||
/* put tables back for PS rexecuting */
|
||||
lex->link_first_table_back(create_table, link_to_local);
|
||||
@@ -1714,8 +1816,7 @@ static bool mysql_test_insert_select(Prepared_statement *stmt,
|
||||
TRUE error, error message is set in THD (but not sent)
|
||||
*/
|
||||
|
||||
static bool check_prepared_statement(Prepared_statement *stmt,
|
||||
bool text_protocol)
|
||||
static bool check_prepared_statement(Prepared_statement *stmt)
|
||||
{
|
||||
THD *thd= stmt->thd;
|
||||
LEX *lex= stmt->lex;
|
||||
@@ -1756,9 +1857,23 @@ static bool check_prepared_statement(Prepared_statement *stmt,
|
||||
case SQLCOM_DELETE:
|
||||
res= mysql_test_delete(stmt, tables);
|
||||
break;
|
||||
|
||||
/* The following allow WHERE clause, so they must be tested like SELECT */
|
||||
case SQLCOM_SHOW_DATABASES:
|
||||
case SQLCOM_SHOW_TABLES:
|
||||
case SQLCOM_SHOW_TRIGGERS:
|
||||
case SQLCOM_SHOW_EVENTS:
|
||||
case SQLCOM_SHOW_OPEN_TABLES:
|
||||
case SQLCOM_SHOW_FIELDS:
|
||||
case SQLCOM_SHOW_KEYS:
|
||||
case SQLCOM_SHOW_COLLATIONS:
|
||||
case SQLCOM_SHOW_CHARSETS:
|
||||
case SQLCOM_SHOW_VARIABLES:
|
||||
case SQLCOM_SHOW_STATUS:
|
||||
case SQLCOM_SHOW_TABLE_STATUS:
|
||||
case SQLCOM_SHOW_STATUS_PROC:
|
||||
case SQLCOM_SHOW_STATUS_FUNC:
|
||||
case SQLCOM_SELECT:
|
||||
res= mysql_test_select(stmt, tables, text_protocol);
|
||||
res= mysql_test_select(stmt, tables);
|
||||
if (res == 2)
|
||||
{
|
||||
/* Statement and field info has already been sent */
|
||||
@@ -1781,6 +1896,9 @@ static bool check_prepared_statement(Prepared_statement *stmt,
|
||||
res= mysql_test_do_fields(stmt, tables, lex->insert_list);
|
||||
break;
|
||||
|
||||
case SQLCOM_CALL:
|
||||
res= mysql_test_call_fields(stmt, tables, &lex->value_list);
|
||||
break;
|
||||
case SQLCOM_SET_OPTION:
|
||||
res= mysql_test_set_fields(stmt, tables, &lex->var_list);
|
||||
break;
|
||||
@@ -1830,7 +1948,6 @@ static bool check_prepared_statement(Prepared_statement *stmt,
|
||||
case SQLCOM_DROP_INDEX:
|
||||
case SQLCOM_ROLLBACK:
|
||||
case SQLCOM_TRUNCATE:
|
||||
case SQLCOM_CALL:
|
||||
case SQLCOM_DROP_VIEW:
|
||||
case SQLCOM_REPAIR:
|
||||
case SQLCOM_ANALYZE:
|
||||
@@ -1873,8 +1990,8 @@ static bool check_prepared_statement(Prepared_statement *stmt,
|
||||
break;
|
||||
}
|
||||
if (res == 0)
|
||||
DBUG_RETURN(text_protocol? FALSE : (send_prep_stmt(stmt, 0) ||
|
||||
thd->protocol->flush()));
|
||||
DBUG_RETURN(stmt->is_protocol_text() ?
|
||||
FALSE : (send_prep_stmt(stmt, 0) || thd->protocol->flush()));
|
||||
error:
|
||||
DBUG_RETURN(TRUE);
|
||||
}
|
||||
@@ -2123,8 +2240,13 @@ void mysql_sql_stmt_prepare(THD *thd)
|
||||
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())
|
||||
if (stmt->is_in_use())
|
||||
{
|
||||
my_error(ER_PS_NO_RECURSION, MYF(0));
|
||||
DBUG_VOID_RETURN;
|
||||
}
|
||||
|
||||
stmt->deallocate();
|
||||
}
|
||||
|
||||
if (! (query= get_dynamic_sql_string(lex, &query_len)) ||
|
||||
@@ -2310,10 +2432,9 @@ void mysql_stmt_execute(THD *thd, char *packet_arg, uint packet_length)
|
||||
ulong flags= (ulong) 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= packet + packet_length;
|
||||
#endif
|
||||
Prepared_statement *stmt;
|
||||
bool open_cursor;
|
||||
DBUG_ENTER("mysql_stmt_execute");
|
||||
|
||||
packet+= 9; /* stmt_id + 5 bytes of flags */
|
||||
@@ -2321,8 +2442,13 @@ void mysql_stmt_execute(THD *thd, char *packet_arg, uint packet_length)
|
||||
/* 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")))
|
||||
if (!(stmt= find_prepared_statement(thd, stmt_id)))
|
||||
{
|
||||
char llbuf[22];
|
||||
my_error(ER_UNKNOWN_STMT_HANDLER, MYF(0), sizeof(llbuf),
|
||||
llstr(stmt_id, llbuf), "mysql_stmt_execute");
|
||||
DBUG_VOID_RETURN;
|
||||
}
|
||||
|
||||
#if defined(ENABLED_PROFILING) && defined(COMMUNITY_SERVER)
|
||||
thd->profiling.set_query_source(stmt->query, stmt->query_length);
|
||||
@@ -2333,43 +2459,10 @@ void mysql_stmt_execute(THD *thd, char *packet_arg, uint packet_length)
|
||||
sp_cache_flush_obsolete(&thd->sp_proc_cache);
|
||||
sp_cache_flush_obsolete(&thd->sp_func_cache);
|
||||
|
||||
#ifndef EMBEDDED_LIBRARY
|
||||
if (stmt->param_count)
|
||||
{
|
||||
uchar *null_array= packet;
|
||||
if (setup_conversion_functions(stmt, &packet, packet_end) ||
|
||||
stmt->set_params(stmt, null_array, packet, packet_end,
|
||||
&expanded_query))
|
||||
goto set_params_data_err;
|
||||
}
|
||||
#else
|
||||
/*
|
||||
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
|
||||
if (!(specialflag & SPECIAL_NO_PRIOR))
|
||||
my_pthread_setprio(pthread_self(),QUERY_PRIOR);
|
||||
open_cursor= test(flags & (ulong) CURSOR_TYPE_READ_ONLY);
|
||||
|
||||
/*
|
||||
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);
|
||||
stmt->execute_loop(&expanded_query, open_cursor, packet, packet_end);
|
||||
|
||||
(void) stmt->execute(&expanded_query,
|
||||
test(flags & (ulong) CURSOR_TYPE_READ_ONLY));
|
||||
if (!(specialflag & SPECIAL_NO_PRIOR))
|
||||
my_pthread_setprio(pthread_self(), WAIT_PRIOR);
|
||||
DBUG_VOID_RETURN;
|
||||
|
||||
set_params_data_err:
|
||||
my_error(ER_WRONG_ARGUMENTS, MYF(0), "mysql_stmt_execute");
|
||||
reset_stmt_params(stmt);
|
||||
DBUG_VOID_RETURN;
|
||||
}
|
||||
|
||||
@@ -2416,24 +2509,8 @@ void mysql_sql_stmt_execute(THD *thd)
|
||||
|
||||
DBUG_PRINT("info",("stmt: 0x%lx", (long) stmt));
|
||||
|
||||
/*
|
||||
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);
|
||||
(void) stmt->execute_loop(&expanded_query, FALSE, NULL, NULL);
|
||||
|
||||
if (stmt->set_params_from_vars(stmt, lex->prepared_stmt_params,
|
||||
&expanded_query))
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -2459,8 +2536,13 @@ void mysql_stmt_fetch(THD *thd, char *packet, uint packet_length)
|
||||
/* First of all clear possible warnings from the previous command */
|
||||
mysql_reset_thd_for_next_command(thd);
|
||||
status_var_increment(thd->status_var.com_stmt_fetch);
|
||||
if (!(stmt= find_prepared_statement(thd, stmt_id, "mysql_stmt_fetch")))
|
||||
if (!(stmt= find_prepared_statement(thd, stmt_id)))
|
||||
{
|
||||
char llbuf[22];
|
||||
my_error(ER_UNKNOWN_STMT_HANDLER, MYF(0), sizeof(llbuf),
|
||||
llstr(stmt_id, llbuf), "mysql_stmt_fetch");
|
||||
DBUG_VOID_RETURN;
|
||||
}
|
||||
|
||||
cursor= stmt->cursor;
|
||||
if (!cursor)
|
||||
@@ -2521,8 +2603,13 @@ void mysql_stmt_reset(THD *thd, char *packet)
|
||||
mysql_reset_thd_for_next_command(thd);
|
||||
|
||||
status_var_increment(thd->status_var.com_stmt_reset);
|
||||
if (!(stmt= find_prepared_statement(thd, stmt_id, "mysql_stmt_reset")))
|
||||
if (!(stmt= find_prepared_statement(thd, stmt_id)))
|
||||
{
|
||||
char llbuf[22];
|
||||
my_error(ER_UNKNOWN_STMT_HANDLER, MYF(0), sizeof(llbuf),
|
||||
llstr(stmt_id, llbuf), "mysql_stmt_reset");
|
||||
DBUG_VOID_RETURN;
|
||||
}
|
||||
|
||||
stmt->close_cursor();
|
||||
|
||||
@@ -2558,15 +2645,15 @@ void mysql_stmt_close(THD *thd, char *packet)
|
||||
|
||||
thd->main_da.disable_status();
|
||||
|
||||
if (!(stmt= find_prepared_statement(thd, stmt_id, "mysql_stmt_close")))
|
||||
if (!(stmt= find_prepared_statement(thd, stmt_id)))
|
||||
DBUG_VOID_RETURN;
|
||||
|
||||
/*
|
||||
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_ASSERT(! stmt->is_in_use());
|
||||
stmt->deallocate();
|
||||
general_log_print(thd, thd->command, NullS);
|
||||
|
||||
DBUG_VOID_RETURN;
|
||||
@@ -2593,14 +2680,15 @@ void mysql_sql_stmt_close(THD *thd)
|
||||
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)
|
||||
else if (stmt->is_in_use())
|
||||
my_error(ER_PS_NO_RECURSION, MYF(0));
|
||||
else
|
||||
{
|
||||
stmt->deallocate();
|
||||
my_ok(thd);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2634,17 +2722,13 @@ void mysql_stmt_get_longdata(THD *thd, char *packet, ulong packet_length)
|
||||
#ifndef EMBEDDED_LIBRARY
|
||||
/* Minimal size of long data packet is 6 bytes */
|
||||
if (packet_length < MYSQL_LONG_DATA_HEADER)
|
||||
{
|
||||
my_error(ER_WRONG_ARGUMENTS, MYF(0), "mysql_stmt_send_long_data");
|
||||
DBUG_VOID_RETURN;
|
||||
}
|
||||
#endif
|
||||
|
||||
stmt_id= uint4korr(packet);
|
||||
packet+= 4;
|
||||
|
||||
if (!(stmt=find_prepared_statement(thd, stmt_id,
|
||||
"mysql_stmt_send_long_data")))
|
||||
if (!(stmt=find_prepared_statement(thd, stmt_id)))
|
||||
DBUG_VOID_RETURN;
|
||||
|
||||
param_number= uint2korr(packet);
|
||||
@@ -2730,7 +2814,7 @@ Select_fetch_protocol_binary::send_data(List<Item> &fields)
|
||||
****************************************************************************/
|
||||
|
||||
Prepared_statement::Prepared_statement(THD *thd_arg, Protocol *protocol_arg)
|
||||
:Statement(&main_lex, &main_mem_root,
|
||||
:Statement(NULL, &main_mem_root,
|
||||
INITIALIZED, ++thd_arg->statement_id_counter),
|
||||
thd(thd_arg),
|
||||
result(thd_arg),
|
||||
@@ -2738,7 +2822,8 @@ Prepared_statement::Prepared_statement(THD *thd_arg, Protocol *protocol_arg)
|
||||
param_array(0),
|
||||
param_count(0),
|
||||
last_errno(0),
|
||||
flags((uint) IS_IN_USE)
|
||||
flags((uint) IS_IN_USE),
|
||||
m_sp_cache_version(0)
|
||||
{
|
||||
init_sql_alloc(&main_mem_root, thd_arg->variables.query_alloc_block_size,
|
||||
thd_arg->variables.query_prealloc_size);
|
||||
@@ -2801,7 +2886,11 @@ Prepared_statement::~Prepared_statement()
|
||||
like Item_param, don't free everything until free_items()
|
||||
*/
|
||||
free_items();
|
||||
delete lex->result;
|
||||
if (lex)
|
||||
{
|
||||
delete lex->result;
|
||||
delete (st_lex_local *) lex;
|
||||
}
|
||||
free_root(&main_mem_root, MYF(0));
|
||||
DBUG_VOID_RETURN;
|
||||
}
|
||||
@@ -2837,6 +2926,34 @@ bool Prepared_statement::set_name(LEX_STRING *name_arg)
|
||||
return name.str == 0;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
Remember the current database.
|
||||
|
||||
We must reset/restore the current database during execution of
|
||||
a prepared statement since it affects execution environment:
|
||||
privileges, @@character_set_database, and other.
|
||||
|
||||
@return Returns an error if out of memory.
|
||||
*/
|
||||
|
||||
bool
|
||||
Prepared_statement::set_db(const char *db_arg, uint db_length_arg)
|
||||
{
|
||||
/* Remember the current database. */
|
||||
if (db_arg && db_length_arg)
|
||||
{
|
||||
db= this->strmake(db_arg, db_length_arg);
|
||||
db_length= db_length_arg;
|
||||
}
|
||||
else
|
||||
{
|
||||
db= NULL;
|
||||
db_length= 0;
|
||||
}
|
||||
return db_arg != NULL && db == NULL;
|
||||
}
|
||||
|
||||
/**************************************************************************
|
||||
Common parts of mysql_[sql]_stmt_prepare, mysql_[sql]_stmt_execute.
|
||||
Essentially, these functions do all the magic of preparing/executing
|
||||
@@ -2878,6 +2995,12 @@ bool Prepared_statement::prepare(const char *packet, uint packet_len)
|
||||
*/
|
||||
status_var_increment(thd->status_var.com_stmt_prepare);
|
||||
|
||||
if (! (lex= new (mem_root) st_lex_local))
|
||||
DBUG_RETURN(TRUE);
|
||||
|
||||
if (set_db(thd->db, thd->db_length))
|
||||
DBUG_RETURN(TRUE);
|
||||
|
||||
/*
|
||||
alloc_query() uses thd->memroot && thd->query, so we should call
|
||||
both of backup_statement() and backup_query_arena() here.
|
||||
@@ -2895,29 +3018,16 @@ bool Prepared_statement::prepare(const char *packet, uint packet_len)
|
||||
old_stmt_arena= thd->stmt_arena;
|
||||
thd->stmt_arena= this;
|
||||
|
||||
Lex_input_stream lip(thd, thd->query, thd->query_length);
|
||||
lip.stmt_prepare_mode= TRUE;
|
||||
Parser_state parser_state(thd, thd->query, thd->query_length);
|
||||
parser_state.m_lip.stmt_prepare_mode= TRUE;
|
||||
lex_start(thd);
|
||||
|
||||
error= parse_sql(thd, &lip, NULL) ||
|
||||
error= parse_sql(thd, & parser_state, NULL) ||
|
||||
thd->is_error() ||
|
||||
init_param_array(this);
|
||||
|
||||
lex->set_trg_event_type_for_tables();
|
||||
|
||||
/* Remember the current database. */
|
||||
|
||||
if (thd->db && thd->db_length)
|
||||
{
|
||||
db= this->strmake(thd->db, thd->db_length);
|
||||
db_length= thd->db_length;
|
||||
}
|
||||
else
|
||||
{
|
||||
db= NULL;
|
||||
db_length= 0;
|
||||
}
|
||||
|
||||
/*
|
||||
While doing context analysis of the query (in check_prepared_statement)
|
||||
we allocate a lot of additional memory: for open tables, JOINs, derived
|
||||
@@ -2941,7 +3051,7 @@ bool Prepared_statement::prepare(const char *packet, uint packet_len)
|
||||
*/
|
||||
|
||||
if (error == 0)
|
||||
error= check_prepared_statement(this, name.str != 0);
|
||||
error= check_prepared_statement(this);
|
||||
|
||||
/*
|
||||
Currently CREATE PROCEDURE/TRIGGER/EVENT are prohibited in prepared
|
||||
@@ -2966,6 +3076,20 @@ bool Prepared_statement::prepare(const char *packet, uint packet_len)
|
||||
init_stmt_after_parse(lex);
|
||||
state= Query_arena::PREPARED;
|
||||
flags&= ~ (uint) IS_IN_USE;
|
||||
/*
|
||||
This is for prepared statement validation purposes.
|
||||
A statement looks up and pre-loads all its stored functions
|
||||
at prepare. Later on, if a function is gone from the cache,
|
||||
execute may fail.
|
||||
Remember the cache version to be able to invalidate the prepared
|
||||
statement at execute if it changes.
|
||||
We only need to care about version of the stored functions cache:
|
||||
if a prepared statement uses a stored procedure, it's indirect,
|
||||
via a stored function. The only exception is SQLCOM_CALL,
|
||||
but the latter one looks up the stored procedure each time
|
||||
it's invoked, rather than once at prepare.
|
||||
*/
|
||||
m_sp_cache_version= sp_cache_version(&thd->sp_func_cache);
|
||||
|
||||
/*
|
||||
Log COM_EXECUTE to the general log. Note, that in case of SQL
|
||||
@@ -2988,6 +3112,306 @@ bool Prepared_statement::prepare(const char *packet, uint packet_len)
|
||||
DBUG_RETURN(error);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
Assign parameter values either from variables, in case of SQL PS
|
||||
or from the execute packet.
|
||||
|
||||
@param expanded_query a container with the original SQL statement.
|
||||
'?' placeholders will be replaced with
|
||||
their values in case of success.
|
||||
The result is used for logging and replication
|
||||
@param packet pointer to execute packet.
|
||||
NULL in case of SQL PS
|
||||
@param packet_end end of the packet. NULL in case of SQL PS
|
||||
|
||||
@todo Use a paremeter source class family instead of 'if's, and
|
||||
support stored procedure variables.
|
||||
|
||||
@retval TRUE an error occurred when assigning a parameter (likely
|
||||
a conversion error or out of memory, or malformed packet)
|
||||
@retval FALSE success
|
||||
*/
|
||||
|
||||
bool
|
||||
Prepared_statement::set_parameters(String *expanded_query,
|
||||
uchar *packet, uchar *packet_end)
|
||||
{
|
||||
bool is_sql_ps= packet == NULL;
|
||||
bool res= FALSE;
|
||||
|
||||
if (is_sql_ps)
|
||||
{
|
||||
/* SQL prepared statement */
|
||||
res= set_params_from_vars(this, thd->lex->prepared_stmt_params,
|
||||
expanded_query);
|
||||
}
|
||||
else if (param_count)
|
||||
{
|
||||
#ifndef EMBEDDED_LIBRARY
|
||||
uchar *null_array= packet;
|
||||
res= (setup_conversion_functions(this, &packet, packet_end) ||
|
||||
set_params(this, null_array, packet, packet_end, expanded_query));
|
||||
#else
|
||||
/*
|
||||
In embedded library we re-install conversion routines each time
|
||||
we set parameters, and also we don't need to parse packet.
|
||||
So we do it in one function.
|
||||
*/
|
||||
res= set_params_data(this, expanded_query);
|
||||
#endif
|
||||
}
|
||||
if (res)
|
||||
{
|
||||
my_error(ER_WRONG_ARGUMENTS, MYF(0),
|
||||
is_sql_ps ? "EXECUTE" : "mysql_stmt_execute");
|
||||
reset_stmt_params(this);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
Execute a prepared statement. Re-prepare it a limited number
|
||||
of times if necessary.
|
||||
|
||||
Try to execute a prepared statement. If there is a metadata
|
||||
validation error, prepare a new copy of the prepared statement,
|
||||
swap the old and the new statements, and try again.
|
||||
If there is a validation error again, repeat the above, but
|
||||
perform no more than MAX_REPREPARE_ATTEMPTS.
|
||||
|
||||
@note We have to try several times in a loop since we
|
||||
release metadata locks on tables after prepared statement
|
||||
prepare. Therefore, a DDL statement may sneak in between prepare
|
||||
and execute of a new statement. If this happens repeatedly
|
||||
more than MAX_REPREPARE_ATTEMPTS times, we give up.
|
||||
|
||||
In future we need to be able to keep the metadata locks between
|
||||
prepare and execute, but right now open_and_lock_tables(), as
|
||||
well as close_thread_tables() are buried deep inside
|
||||
execution code (mysql_execute_command()).
|
||||
|
||||
@return TRUE if an error, FALSE if success
|
||||
@retval TRUE either MAX_REPREPARE_ATTEMPTS has been reached,
|
||||
or some general error
|
||||
@retval FALSE successfully executed the statement, perhaps
|
||||
after having reprepared it a few times.
|
||||
*/
|
||||
|
||||
bool
|
||||
Prepared_statement::execute_loop(String *expanded_query,
|
||||
bool open_cursor,
|
||||
uchar *packet,
|
||||
uchar *packet_end)
|
||||
{
|
||||
const int MAX_REPREPARE_ATTEMPTS= 3;
|
||||
Reprepare_observer reprepare_observer;
|
||||
bool error;
|
||||
int reprepare_attempt= 0;
|
||||
|
||||
if (set_parameters(expanded_query, packet, packet_end))
|
||||
return TRUE;
|
||||
|
||||
reexecute:
|
||||
reprepare_observer.reset_reprepare_observer();
|
||||
|
||||
/*
|
||||
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);
|
||||
|
||||
/*
|
||||
Install the metadata observer. If some metadata version is
|
||||
different from prepare time and an observer is installed,
|
||||
the observer method will be invoked to push an error into
|
||||
the error stack.
|
||||
*/
|
||||
if (sql_command_flags[lex->sql_command] &
|
||||
CF_REEXECUTION_FRAGILE)
|
||||
{
|
||||
DBUG_ASSERT(thd->m_reprepare_observer == NULL);
|
||||
thd->m_reprepare_observer = &reprepare_observer;
|
||||
}
|
||||
|
||||
if (!(specialflag & SPECIAL_NO_PRIOR))
|
||||
my_pthread_setprio(pthread_self(),QUERY_PRIOR);
|
||||
|
||||
error= execute(expanded_query, open_cursor) || thd->is_error();
|
||||
|
||||
if (!(specialflag & SPECIAL_NO_PRIOR))
|
||||
my_pthread_setprio(pthread_self(), WAIT_PRIOR);
|
||||
|
||||
thd->m_reprepare_observer= NULL;
|
||||
|
||||
if (error && !thd->is_fatal_error && !thd->killed &&
|
||||
reprepare_observer.is_invalidated() &&
|
||||
reprepare_attempt++ < MAX_REPREPARE_ATTEMPTS)
|
||||
{
|
||||
DBUG_ASSERT(thd->main_da.sql_errno() == ER_NEED_REPREPARE);
|
||||
thd->clear_error();
|
||||
|
||||
error= reprepare();
|
||||
|
||||
if (! error) /* Success */
|
||||
goto reexecute;
|
||||
}
|
||||
reset_stmt_params(this);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
Reprepare this prepared statement.
|
||||
|
||||
Currently this is implemented by creating a new prepared
|
||||
statement, preparing it with the original query and then
|
||||
swapping the new statement and the original one.
|
||||
|
||||
@retval TRUE an error occurred. Possible errors include
|
||||
incompatibility of new and old result set
|
||||
metadata
|
||||
@retval FALSE success, the statement has been reprepared
|
||||
*/
|
||||
|
||||
bool
|
||||
Prepared_statement::reprepare()
|
||||
{
|
||||
char saved_cur_db_name_buf[NAME_LEN+1];
|
||||
LEX_STRING saved_cur_db_name=
|
||||
{ saved_cur_db_name_buf, sizeof(saved_cur_db_name_buf) };
|
||||
LEX_STRING stmt_db_name= { db, db_length };
|
||||
bool cur_db_changed;
|
||||
bool error;
|
||||
|
||||
Prepared_statement copy(thd, &thd->protocol_text);
|
||||
|
||||
status_var_increment(thd->status_var.com_stmt_reprepare);
|
||||
|
||||
if (mysql_opt_change_db(thd, &stmt_db_name, &saved_cur_db_name, TRUE,
|
||||
&cur_db_changed))
|
||||
return TRUE;
|
||||
|
||||
error= (name.str && copy.set_name(&name) ||
|
||||
copy.prepare(query, query_length) ||
|
||||
validate_metadata(©));
|
||||
|
||||
if (cur_db_changed)
|
||||
mysql_change_db(thd, &saved_cur_db_name, TRUE);
|
||||
|
||||
if (! error)
|
||||
{
|
||||
swap_prepared_statement(©);
|
||||
swap_parameter_array(param_array, copy.param_array, param_count);
|
||||
#ifndef DBUG_OFF
|
||||
is_reprepared= TRUE;
|
||||
#endif
|
||||
/*
|
||||
Clear possible warnings during reprepare, it has to be completely
|
||||
transparent to the user. We use mysql_reset_errors() since
|
||||
there were no separate query id issued for re-prepare.
|
||||
Sic: we can't simply silence warnings during reprepare, because if
|
||||
it's failed, we need to return all the warnings to the user.
|
||||
*/
|
||||
mysql_reset_errors(thd, TRUE);
|
||||
}
|
||||
return error;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
Validate statement result set metadata (if the statement returns
|
||||
a result set).
|
||||
|
||||
Currently we only check that the number of columns of the result
|
||||
set did not change.
|
||||
This is a helper method used during re-prepare.
|
||||
|
||||
@param[in] copy the re-prepared prepared statement to verify
|
||||
the metadata of
|
||||
|
||||
@retval TRUE error, ER_PS_REBIND is reported
|
||||
@retval FALSE statement return no or compatible metadata
|
||||
*/
|
||||
|
||||
|
||||
bool Prepared_statement::validate_metadata(Prepared_statement *copy)
|
||||
{
|
||||
/**
|
||||
If this is an SQL prepared statement or EXPLAIN,
|
||||
return FALSE -- the metadata of the original SELECT,
|
||||
if any, has not been sent to the client.
|
||||
*/
|
||||
if (is_protocol_text() || lex->describe)
|
||||
return FALSE;
|
||||
|
||||
if (lex->select_lex.item_list.elements !=
|
||||
copy->lex->select_lex.item_list.elements)
|
||||
{
|
||||
/** Column counts mismatch, update the client */
|
||||
thd->server_status|= SERVER_STATUS_METADATA_CHANGED;
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
Replace the original prepared statement with a prepared copy.
|
||||
|
||||
This is a private helper that is used as part of statement
|
||||
reprepare
|
||||
|
||||
@return This function does not return any errors.
|
||||
*/
|
||||
|
||||
void
|
||||
Prepared_statement::swap_prepared_statement(Prepared_statement *copy)
|
||||
{
|
||||
Statement tmp_stmt;
|
||||
|
||||
/* Swap memory roots. */
|
||||
swap_variables(MEM_ROOT, main_mem_root, copy->main_mem_root);
|
||||
|
||||
/* Swap the arenas */
|
||||
tmp_stmt.set_query_arena(this);
|
||||
set_query_arena(copy);
|
||||
copy->set_query_arena(&tmp_stmt);
|
||||
|
||||
/* Swap the statement parent classes */
|
||||
tmp_stmt.set_statement(this);
|
||||
set_statement(copy);
|
||||
copy->set_statement(&tmp_stmt);
|
||||
|
||||
/* Swap ids back, we need the original id */
|
||||
swap_variables(ulong, id, copy->id);
|
||||
/* Swap mem_roots back, they must continue pointing at the main_mem_roots */
|
||||
swap_variables(MEM_ROOT *, mem_root, copy->mem_root);
|
||||
/*
|
||||
Swap the old and the new parameters array. The old array
|
||||
is allocated in the old arena.
|
||||
*/
|
||||
swap_variables(Item_param **, param_array, copy->param_array);
|
||||
/* Swap flags: this is perhaps unnecessary */
|
||||
swap_variables(uint, flags, copy->flags);
|
||||
/* Swap names, the old name is allocated in the wrong memory root */
|
||||
swap_variables(LEX_STRING, name, copy->name);
|
||||
/* Ditto */
|
||||
swap_variables(char *, db, copy->db);
|
||||
swap_variables(ulong, m_sp_cache_version, copy->m_sp_cache_version);
|
||||
|
||||
DBUG_ASSERT(db_length == copy->db_length);
|
||||
DBUG_ASSERT(param_count == copy->param_count);
|
||||
DBUG_ASSERT(thd == copy->thd);
|
||||
last_error[0]= '\0';
|
||||
last_errno= 0;
|
||||
/* Do not swap protocols, the copy always has protocol_text */
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
Execute a prepared statement.
|
||||
|
||||
@@ -3037,6 +3461,19 @@ bool Prepared_statement::execute(String *expanded_query, bool open_cursor)
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/*
|
||||
Reprepare the statement if we're using stored functions
|
||||
and the version of the stored routines cache has changed.
|
||||
*/
|
||||
if (lex->uses_stored_routines() &&
|
||||
m_sp_cache_version != sp_cache_version(&thd->sp_func_cache) &&
|
||||
thd->m_reprepare_observer &&
|
||||
thd->m_reprepare_observer->report_error(thd))
|
||||
{
|
||||
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
|
||||
@@ -3150,10 +3587,7 @@ bool Prepared_statement::execute(String *expanded_query, bool open_cursor)
|
||||
DBUG_ASSERT(! (error && cursor));
|
||||
|
||||
if (! cursor)
|
||||
{
|
||||
cleanup_stmt();
|
||||
reset_stmt_params(this);
|
||||
}
|
||||
|
||||
thd->set_statement(&stmt_backup);
|
||||
thd->stmt_arena= old_stmt_arena;
|
||||
@@ -3187,16 +3621,10 @@ error:
|
||||
|
||||
/** Common part of DEALLOCATE PREPARE and mysql_stmt_close. */
|
||||
|
||||
bool Prepared_statement::deallocate()
|
||||
void Prepared_statement::deallocate()
|
||||
{
|
||||
/* We account deallocate in the same manner as mysql_stmt_close */
|
||||
status_var_increment(thd->status_var.com_stmt_close);
|
||||
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;
|
||||
}
|
||||
|
Reference in New Issue
Block a user