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.cc245
1 files changed, 169 insertions, 76 deletions
diff --git a/sql/sql_prepare.cc b/sql/sql_prepare.cc
index e493ded0b8a..65adf57bb02 100644
--- a/sql/sql_prepare.cc
+++ b/sql/sql_prepare.cc
@@ -112,7 +112,6 @@ public:
/****************************************************************************/
/**
- @class Prepared_statement
@brief Prepared_statement: a statement that can contain placeholders
*/
@@ -229,7 +228,7 @@ find_prepared_statement(THD *thd, ulong id, const char *where)
static bool send_prep_stmt(Prepared_statement *stmt, uint columns)
{
NET *net= &stmt->thd->net;
- char buff[12];
+ uchar buff[12];
uint tmp;
DBUG_ENTER("send_prep_stmt");
@@ -572,6 +571,8 @@ void set_param_date(Item_param *param, uchar **pos, ulong len)
static void set_param_str(Item_param *param, uchar **pos, ulong len)
{
ulong length= get_param_length(pos, len);
+ if (length > len)
+ length= len;
param->set_str((const char *)*pos, length);
*pos+= length;
}
@@ -742,6 +743,8 @@ static bool insert_params_with_log(Prepared_statement *stmt, uchar *null_array,
if (read_pos >= data_end)
DBUG_RETURN(1);
param->set_param_func(param, &read_pos, data_end - read_pos);
+ if (param->state == Item_param::NO_VALUE)
+ DBUG_RETURN(1);
}
}
res= param->query_val_str(&str);
@@ -778,6 +781,8 @@ static bool insert_params(Prepared_statement *stmt, uchar *null_array,
if (read_pos >= data_end)
DBUG_RETURN(1);
param->set_param_func(param, &read_pos, data_end - read_pos);
+ if (param->state == Item_param::NO_VALUE)
+ DBUG_RETURN(1);
}
}
if (param->convert_str_value(stmt->thd))
@@ -860,6 +865,8 @@ static bool emb_insert_params(Prepared_statement *stmt, String *expanded_query)
client_param->length ?
*client_param->length :
client_param->buffer_length);
+ if (param->state == Item_param::NO_VALUE)
+ DBUG_RETURN(1);
}
}
if (param->convert_str_value(thd))
@@ -902,6 +909,8 @@ static bool emb_insert_params_with_log(Prepared_statement *stmt,
client_param->length ?
*client_param->length :
client_param->buffer_length);
+ if (param->state == Item_param::NO_VALUE)
+ DBUG_RETURN(1);
}
}
res= param->query_val_str(&str);
@@ -946,7 +955,7 @@ static bool insert_params_from_vars(Prepared_statement *stmt,
Item_param *param= *it;
varname= var_it++;
entry= (user_var_entry*)hash_search(&stmt->thd->user_vars,
- (byte*) varname->str,
+ (uchar*) varname->str,
varname->length);
if (param->set_from_user_var(stmt->thd, entry) ||
param->convert_str_value(stmt->thd))
@@ -981,6 +990,7 @@ static bool insert_params_from_vars_with_log(Prepared_statement *stmt,
String buf;
const String *val;
uint32 length= 0;
+ THD *thd= stmt->thd;
DBUG_ENTER("insert_params_from_vars");
@@ -991,34 +1001,20 @@ 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, stmt->lex->sql_command,
- *varname, &entry))
- DBUG_RETURN(1);
- if (param->set_from_user_var(stmt->thd, entry))
+ entry= (user_var_entry *) hash_search(&thd->user_vars, (uchar*) varname->str,
+ varname->length);
+ /*
+ We have to call the setup_one_conversion_function() here to set
+ the parameter's members that might be needed further
+ (e.g. value.cs_info.character_set_client is used in the query_val_str()).
+ */
+ setup_one_conversion_function(thd, param, param->param_type);
+ if (param->set_from_user_var(thd, entry))
DBUG_RETURN(1);
- /* Insert @'escaped-varname' instead of parameter in the query */
- if (entry)
- {
- char *start, *ptr;
- buf.length(0);
- if (buf.reserve(entry->name.length*2+3))
- DBUG_RETURN(1);
-
- start= ptr= buf.c_ptr_quick();
- *ptr++= '@';
- *ptr++= '\'';
- ptr+= escape_string_for_mysql(&my_charset_utf8_general_ci,
- ptr, 0, entry->name.str,
- entry->name.length);
- *ptr++= '\'';
- buf.length(ptr - start);
- val= &buf;
- }
- else
- val= &my_null_string;
+ val= param->query_val_str(&buf);
- if (param->convert_str_value(stmt->thd))
+ if (param->convert_str_value(thd))
DBUG_RETURN(1); /* out of memory */
if (query->replace(param->pos_in_query+length, 1, *val))
@@ -1077,7 +1073,7 @@ static bool mysql_test_insert(Prepared_statement *stmt,
if (table_list->table)
{
// don't allocate insert_values
- table_list->table->insert_values=(byte *)1;
+ table_list->table->insert_values=(uchar *)1;
}
if (mysql_prepare_insert(thd, table_list, table_list->table,
@@ -1164,8 +1160,9 @@ static int mysql_test_update(Prepared_statement *stmt,
goto error;
#ifndef NO_EMBEDDED_ACCESS_CHECKS
- /* TABLE_LIST contain right privilages request */
- want_privilege= table_list->grant.want_privilege;
+ /* Force privilege re-checking for views after they have been opened. */
+ want_privilege= (table_list->view ? UPDATE_ACL :
+ table_list->grant.want_privilege);
#endif
if (mysql_prepare_update(thd, table_list, &select->where,
@@ -1492,8 +1489,21 @@ static bool mysql_test_create_table(Prepared_statement *stmt)
if (select_lex->item_list.elements)
{
+ if (!(lex->create_info.options & HA_LEX_CREATE_TMP_TABLE))
+ {
+ lex->link_first_table_back(create_table, link_to_local);
+ create_table->create= TRUE;
+ }
+
+ if (open_normal_and_derived_tables(stmt->thd, lex->query_tables, 0))
+ DBUG_RETURN(TRUE);
+
+ if (!(lex->create_info.options & HA_LEX_CREATE_TMP_TABLE))
+ create_table= lex->unlink_first_table(&link_to_local);
+
select_lex->context.resolve_in_select_list= TRUE;
- res= select_like_stmt_test_with_open(stmt, tables, 0, 0);
+
+ res= select_like_stmt_test(stmt, 0, 0);
}
/* put tables back for PS rexecuting */
@@ -1590,7 +1600,7 @@ static bool mysql_insert_select_prepare_tester(THD *thd)
next_local;
/* Skip first table, which is the table we are inserting in */
- first_select->table_list.first= (byte *) second_table;
+ first_select->table_list.first= (uchar *) second_table;
thd->lex->select_lex.context.table_list=
thd->lex->select_lex.context.first_name_resolution_table= second_table;
@@ -1621,7 +1631,7 @@ static bool mysql_test_insert_select(Prepared_statement *stmt,
if (tables->table)
{
// don't allocate insert_values
- tables->table->insert_values=(byte *)1;
+ tables->table->insert_values=(uchar *)1;
}
if (insert_precheck(stmt->thd, tables))
@@ -1636,7 +1646,7 @@ static bool mysql_test_insert_select(Prepared_statement *stmt,
&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.table_list.first= (uchar*) first_local_table;
return res;
}
@@ -1715,6 +1725,13 @@ static bool check_prepared_statement(Prepared_statement *stmt,
res= mysql_test_create_table(stmt);
break;
+ case SQLCOM_CREATE_VIEW:
+ if (lex->create_view_mode == VIEW_ALTER)
+ {
+ my_message(ER_UNSUPPORTED_PS, ER(ER_UNSUPPORTED_PS), MYF(0));
+ goto error;
+ }
+ break;
case SQLCOM_DO:
res= mysql_test_do_fields(stmt, tables, lex->insert_list);
break;
@@ -1751,6 +1768,7 @@ static bool check_prepared_statement(Prepared_statement *stmt,
case SQLCOM_SHOW_CREATE_PROC:
case SQLCOM_SHOW_CREATE_FUNC:
case SQLCOM_SHOW_CREATE_EVENT:
+ case SQLCOM_SHOW_CREATE_TRIGGER:
case SQLCOM_SHOW_CREATE:
case SQLCOM_SHOW_PROC_CODE:
case SQLCOM_SHOW_FUNC_CODE:
@@ -1768,7 +1786,6 @@ static bool check_prepared_statement(Prepared_statement *stmt,
case SQLCOM_ROLLBACK:
case SQLCOM_TRUNCATE:
case SQLCOM_CALL:
- case SQLCOM_CREATE_VIEW:
case SQLCOM_DROP_VIEW:
case SQLCOM_REPAIR:
case SQLCOM_ANALYZE:
@@ -1794,6 +1811,9 @@ static bool check_prepared_statement(Prepared_statement *stmt,
case SQLCOM_KILL:
break;
+ case SQLCOM_PREPARE:
+ case SQLCOM_EXECUTE:
+ case SQLCOM_DEALLOCATE_PREPARE:
default:
/*
Trivial check of all status commands. This is easier than having
@@ -1920,13 +1940,6 @@ void mysql_stmt_prepare(THD *thd, const char *packet, uint packet_length)
/* Statement map deletes statement on erase */
thd->stmt_map.erase(stmt);
}
- else
- {
- const char *format= "[%lu] %.*b";
- general_log_print(thd, COM_STMT_PREPARE, format, stmt->id,
- stmt->query_length, stmt->query);
-
- }
/* check_prepared_statemnt sends the metadata packet in case of success */
DBUG_VOID_RETURN;
}
@@ -1970,7 +1983,7 @@ static const char *get_dynamic_sql_string(LEX *lex, uint *query_len)
*/
if ((entry=
(user_var_entry*)hash_search(&thd->user_vars,
- (byte*)lex->prepared_stmt_code.str,
+ (uchar*)lex->prepared_stmt_code.str,
lex->prepared_stmt_code.length))
&& entry->value)
{
@@ -1999,7 +2012,7 @@ static const char *get_dynamic_sql_string(LEX *lex, uint *query_len)
len= (needs_conversion ? var_value->length() * to_cs->mbmaxlen :
var_value->length());
- if (!(query_str= alloc_root(thd->mem_root, len+1)))
+ if (!(query_str= (char*) alloc_root(thd->mem_root, len+1)))
goto end;
if (needs_conversion)
@@ -2312,12 +2325,6 @@ void mysql_stmt_execute(THD *thd, char *packet_arg, uint packet_length)
test(flags & (ulong) CURSOR_TYPE_READ_ONLY));
if (!(specialflag & SPECIAL_NO_PRIOR))
my_pthread_setprio(pthread_self(), WAIT_PRIOR);
- if (error == 0)
- {
- const char *format= "[%lu] %.*b";
- general_log_print(thd, COM_STMT_EXECUTE, format, stmt->id,
- thd->query_length, thd->query);
- }
DBUG_VOID_RETURN;
set_params_data_err:
@@ -2355,7 +2362,7 @@ void mysql_sql_stmt_execute(THD *thd)
/* 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));
+ DBUG_PRINT("info", ("EXECUTE: %.*s\n", (int) name->length, name->str));
if (!(stmt= (Prepared_statement*) thd->stmt_map.find_by_name(name)))
{
@@ -2416,7 +2423,7 @@ 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);
- statistic_increment(thd->status_var.com_stmt_fetch, &LOCK_status);
+ status_var_increment(thd->status_var.com_stmt_fetch);
if (!(stmt= find_prepared_statement(thd, stmt_id, "mysql_stmt_fetch")))
DBUG_VOID_RETURN;
@@ -2480,7 +2487,7 @@ void mysql_stmt_reset(THD *thd, char *packet)
/* 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);
+ status_var_increment(thd->status_var.com_stmt_reset);
if (!(stmt= find_prepared_statement(thd, stmt_id, "mysql_stmt_reset")))
DBUG_VOID_RETURN;
@@ -2543,7 +2550,8 @@ 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));
+ DBUG_PRINT("info", ("DEALLOCATE PREPARE: %.*s\n", (int) name->length,
+ name->str));
if (! (stmt= (Prepared_statement*) thd->stmt_map.find_by_name(name)))
{
@@ -2584,7 +2592,7 @@ void mysql_stmt_get_longdata(THD *thd, char *packet, ulong packet_length)
#endif
DBUG_ENTER("mysql_stmt_get_longdata");
- statistic_increment(thd->status_var.com_stmt_send_long_data, &LOCK_status);
+ status_var_increment(thd->status_var.com_stmt_send_long_data);
#ifndef EMBEDDED_LIBRARY
/* Minimal size of long data packet is 6 bytes */
if (packet_length <= MYSQL_LONG_DATA_HEADER)
@@ -2788,7 +2796,7 @@ void Prepared_statement::cleanup_stmt()
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);
+ name.str= (char*) memdup_root(mem_root, name_arg->str, name_arg->length);
return name.str == 0;
}
@@ -2835,7 +2843,7 @@ bool Prepared_statement::prepare(const char *packet, uint packet_len)
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);
+ status_var_increment(thd->status_var.com_stmt_prepare);
/*
alloc_query() uses thd->memroot && thd->query, so we should call
@@ -2853,11 +2861,28 @@ bool Prepared_statement::prepare(const char *packet, uint packet_len)
old_stmt_arena= thd->stmt_arena;
thd->stmt_arena= this;
- lex_start(thd, thd->query, thd->query_length);
- lex->stmt_prepare_mode= TRUE;
- error= MYSQLparse((void *)thd) || thd->is_fatal_error ||
- thd->net.report_error || init_param_array(this);
+ Lex_input_stream lip(thd, thd->query, thd->query_length);
+ lip.stmt_prepare_mode= TRUE;
+ lex_start(thd);
+
+ error= parse_sql(thd, &lip, NULL) ||
+ thd->net.report_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)
@@ -2901,24 +2926,35 @@ bool Prepared_statement::prepare(const char *packet, uint packet_len)
thd->restore_backup_statement(this, &stmt_backup);
thd->stmt_arena= old_stmt_arena;
- if ((protocol->type() == Protocol::PROTOCOL_TEXT) && (param_count > 0))
- {
- /*
- This is a mysql_sql_stmt_prepare(); query expansion will insert user
- variable references, and user variables are uncacheable, thus we have to
- mark this statement as uncacheable.
- This has to be done before setup_set_params(), as it may make expansion
- unneeded.
- */
- lex->safe_to_cache_query= FALSE;
- }
-
if (error == 0)
{
setup_set_params();
init_stmt_after_parse(lex);
state= Query_arena::PREPARED;
flags&= ~ (uint) IS_IN_USE;
+
+ /*
+ Log COM_EXECUTE to the general log. Note, that in case of SQL
+ prepared statements this causes two records to be output:
+
+ Query PREPARE stmt from @user_variable
+ Prepare <statement SQL text>
+
+ This is considered user-friendly, since in the
+ second log entry we output the actual statement text.
+
+ Do not print anything if this is an SQL prepared statement and
+ we're inside a stored procedure (also called Dynamic SQL) --
+ sub-statements inside stored procedures are not logged into
+ the general log.
+ */
+ if (thd->spcont == NULL)
+ {
+ const char *format= "[%lu] %.*b";
+ general_log_print(thd, COM_STMT_PREPARE, format, id,
+ query_length, query);
+
+ }
}
DBUG_RETURN(error);
}
@@ -2954,7 +2990,14 @@ bool Prepared_statement::execute(String *expanded_query, bool open_cursor)
Query_arena *old_stmt_arena;
bool error= TRUE;
- statistic_increment(thd->status_var.com_stmt_execute, &LOCK_status);
+ 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) };
+ bool cur_db_changed;
+
+ LEX_STRING stmt_db_name= { db, db_length };
+
+ status_var_increment(thd->status_var.com_stmt_execute);
/* Check if we got an error when sending long data */
if (state == Query_arena::ERROR)
@@ -3002,6 +3045,21 @@ bool Prepared_statement::execute(String *expanded_query, bool open_cursor)
*/
thd->set_n_backup_statement(this, &stmt_backup);
+
+ /*
+ Change the current database (if needed).
+
+ Force switching, because the database of the prepared statement may be
+ NULL (prepared statements can be created while no current database
+ selected).
+ */
+
+ if (mysql_opt_change_db(thd, &stmt_db_name, &saved_cur_db_name, TRUE,
+ &cur_db_changed))
+ goto error;
+
+ /* Allocate query. */
+
if (expanded_query->length() &&
alloc_query(thd, (char*) expanded_query->ptr(),
expanded_query->length()+1))
@@ -3030,6 +3088,8 @@ bool Prepared_statement::execute(String *expanded_query, bool open_cursor)
thd->protocol= protocol; /* activate stmt protocol */
+ /* Go! */
+
if (open_cursor)
error= mysql_open_cursor(thd, (uint) ALWAYS_MATERIALIZED_CURSOR,
&result, &cursor);
@@ -3048,6 +3108,17 @@ bool Prepared_statement::execute(String *expanded_query, bool open_cursor)
}
}
+ /*
+ Restore the current database (if changed).
+
+ Force switching back to the saved current database (if changed),
+ because it may be NULL. In this case, mysql_change_db() would generate
+ an error.
+ */
+
+ if (cur_db_changed)
+ mysql_change_db(thd, &saved_cur_db_name, TRUE);
+
thd->protocol= &thd->protocol_text; /* use normal protocol */
/* Assert that if an error, no cursor is open */
@@ -3065,6 +3136,28 @@ bool Prepared_statement::execute(String *expanded_query, bool open_cursor)
if (state == Query_arena::PREPARED)
state= Query_arena::EXECUTED;
+ /*
+ Log COM_EXECUTE to the general log. Note, that in case of SQL
+ prepared statements this causes two records to be output:
+
+ Query EXECUTE <statement name>
+ Execute <statement SQL text>
+
+ This is considered user-friendly, since in the
+ second log entry we output values of parameter markers.
+
+ Do not print anything if this is an SQL prepared statement and
+ we're inside a stored procedure (also called Dynamic SQL) --
+ sub-statements inside stored procedures are not logged into
+ the general log.
+ */
+ if (error == 0 && thd->spcont == NULL)
+ {
+ const char *format= "[%lu] %.*b";
+ general_log_print(thd, COM_STMT_EXECUTE, format, id,
+ thd->query_length, thd->query);
+ }
+
error:
flags&= ~ (uint) IS_IN_USE;
return error;
@@ -3076,7 +3169,7 @@ error:
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);
+ status_var_increment(thd->status_var.com_stmt_close);
if (flags & (uint) IS_IN_USE)
{
my_error(ER_PS_NO_RECURSION, MYF(0));