diff options
Diffstat (limited to 'sql/sql_prepare.cc')
-rw-r--r-- | sql/sql_prepare.cc | 245 |
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)); |