diff options
Diffstat (limited to 'sql/sp_head.cc')
-rw-r--r-- | sql/sp_head.cc | 1360 |
1 files changed, 926 insertions, 434 deletions
diff --git a/sql/sp_head.cc b/sql/sp_head.cc index 7816af398c2..4e247859d83 100644 --- a/sql/sp_head.cc +++ b/sql/sp_head.cc @@ -23,8 +23,6 @@ #include "probes_mysql.h" #include "sql_show.h" // append_identifier #include "sql_db.h" // mysql_opt_change_db, mysql_change_db -#include "sql_table.h" // sp_prepare_create_field, - // prepare_create_field #include "sql_acl.h" // *_ACL #include "sql_array.h" // Dynamic_array #include "log_event.h" // Query_log_event @@ -72,8 +70,12 @@ static void reset_start_time_for_sp(THD *thd) Item::Type -sp_map_item_type(enum enum_field_types type) +sp_map_item_type(const Type_handler *handler) { + if (handler == &type_handler_row) + return Item::ROW_ITEM; + enum_field_types type= real_type_to_type(handler->real_field_type()); + switch (type) { case MYSQL_TYPE_BIT: case MYSQL_TYPE_TINY: @@ -94,66 +96,45 @@ sp_map_item_type(enum enum_field_types type) } -/** - Return a string representation of the Item value. - - @param thd thread handle - @param str string buffer for representation of the value - - @note - If the item has a string result type, the string is escaped - according to its character set. +bool Item_splocal::append_for_log(THD *thd, String *str) +{ + if (fix_fields(thd, NULL)) + return true; - @retval - NULL on error - @retval - non-NULL a pointer to valid a valid string on success -*/ + if (limit_clause_param) + return str->append_ulonglong(val_uint()); -static String * -sp_get_item_value(THD *thd, Item *item, String *str) -{ - switch (item->result_type()) { - case REAL_RESULT: - case INT_RESULT: - case DECIMAL_RESULT: - if (item->field_type() != MYSQL_TYPE_BIT) - return item->val_str(str); - /* fall through */ - case STRING_RESULT: - { - String *result= item->val_str(str); + /* + ROW variables are currently not allowed in select_list, e.g.: + SELECT row_variable; + ROW variables can appear in query parts where name is not important, e.g.: + SELECT ROW(1,2)=row_variable FROM t1; + So we can skip using NAME_CONST() and use ROW() constants directly. + */ + if (type_handler() == &type_handler_row) + return append_value_for_log(thd, str); - if (!result) - return NULL; + if (str->append(STRING_WITH_LEN(" NAME_CONST('")) || + str->append(&m_name) || + str->append(STRING_WITH_LEN("',"))) + return true; + return append_value_for_log(thd, str) || str->append(')'); +} - { - StringBuffer<STRING_BUFFER_USUAL_SIZE> buf(result->charset()); - CHARSET_INFO *cs= thd->variables.character_set_client; - - buf.append('_'); - buf.append(result->charset()->csname); - if (cs->escape_with_backslash_is_dangerous) - buf.append(' '); - append_query_string(cs, &buf, result->ptr(), result->length(), - thd->variables.sql_mode & MODE_NO_BACKSLASH_ESCAPES); - buf.append(" COLLATE '"); - buf.append(item->collation.collation->name); - buf.append('\''); - str->copy(buf); - - return str; - } - } - case ROW_RESULT: - default: - return NULL; - } +bool Item_splocal::append_value_for_log(THD *thd, String *str) +{ + StringBuffer<STRING_BUFFER_USUAL_SIZE> str_value_holder(&my_charset_latin1); + Item *item= this_item(); + String *str_value= item->type_handler()->print_item_value(thd, item, + &str_value_holder); + return str_value ? + str->append(*str_value) : + str->append(STRING_WITH_LEN("NULL")); } -bool Item_splocal::append_for_log(THD *thd, String *str) +bool Item_splocal_row_field::append_for_log(THD *thd, String *str) { if (fix_fields(thd, NULL)) return true; @@ -163,15 +144,11 @@ bool Item_splocal::append_for_log(THD *thd, String *str) if (str->append(STRING_WITH_LEN(" NAME_CONST('")) || str->append(&m_name) || + str->append(".") || + str->append(&m_field_name) || str->append(STRING_WITH_LEN("',"))) return true; - - StringBuffer<STRING_BUFFER_USUAL_SIZE> str_value_holder(&my_charset_latin1); - String *str_value= sp_get_item_value(thd, this_item(), &str_value_holder); - if (str_value) - return str->append(*str_value) || str->append(')'); - else - return str->append(STRING_WITH_LEN("NULL)")); + return append_value_for_log(thd, str) || str->append(')'); } @@ -261,12 +238,14 @@ sp_get_flags_for_command(LEX *lex) flags= sp_head::CONTAINS_DYNAMIC_SQL; break; case SQLCOM_CREATE_TABLE: + case SQLCOM_CREATE_SEQUENCE: if (lex->tmp_table()) flags= 0; else flags= sp_head::HAS_COMMIT_OR_ROLLBACK; break; case SQLCOM_DROP_TABLE: + case SQLCOM_DROP_SEQUENCE: if (lex->tmp_table()) flags= 0; else @@ -285,6 +264,7 @@ sp_get_flags_for_command(LEX *lex) case SQLCOM_CREATE_USER: case SQLCOM_CREATE_ROLE: case SQLCOM_ALTER_TABLE: + case SQLCOM_ALTER_SEQUENCE: case SQLCOM_ALTER_USER: case SQLCOM_GRANT: case SQLCOM_GRANT_ROLE: @@ -367,14 +347,20 @@ sp_get_flags_for_command(LEX *lex) */ Item * -sp_prepare_func_item(THD* thd, Item **it_addr) +sp_prepare_func_item(THD* thd, Item **it_addr, uint cols) { DBUG_ENTER("sp_prepare_func_item"); + if (!(*it_addr)->fixed && + (*it_addr)->fix_fields(thd, it_addr)) + { + DBUG_PRINT("info", ("fix_fields() failed")); + DBUG_RETURN(NULL); + } it_addr= (*it_addr)->this_item_addr(thd, it_addr); - if (!(*it_addr)->fixed && - ((*it_addr)->fix_fields(thd, it_addr) || - (*it_addr)->check_cols(1))) + if ((!(*it_addr)->fixed && + (*it_addr)->fix_fields(thd, it_addr)) || + (*it_addr)->check_cols(cols)) { DBUG_PRINT("info", ("fix_fields() failed")); DBUG_RETURN(NULL); @@ -397,7 +383,8 @@ sp_prepare_func_item(THD* thd, Item **it_addr) */ bool -sp_eval_expr(THD *thd, Field *result_field, Item **expr_item_ptr) +sp_eval_expr(THD *thd, Item *result_item, Field *result_field, + Item **expr_item_ptr) { Item *expr_item; enum_check_fields save_count_cuted_fields= thd->count_cuted_fields; @@ -410,10 +397,22 @@ sp_eval_expr(THD *thd, Field *result_field, Item **expr_item_ptr) if (!*expr_item_ptr) goto error; - if (!(expr_item= sp_prepare_func_item(thd, expr_item_ptr))) + if (!(expr_item= sp_prepare_func_item(thd, expr_item_ptr, + result_item ? result_item->cols() : 1))) goto error; /* + expr_item is now fixed, it's safe to call cmp_type() + If result_item is NULL, then we're setting the RETURN value. + */ + if ((!result_item || result_item->cmp_type() != ROW_RESULT) && + expr_item->cmp_type() == ROW_RESULT) + { + my_error(ER_OPERAND_COLUMNS, MYF(0), 1); + goto error; + } + + /* Set THD flags to emit warnings/errors in case of overflow/type errors during saving the item into the field. @@ -460,42 +459,14 @@ error: */ sp_name::sp_name(const MDL_key *key, char *qname_buff) + :Database_qualified_name(key->db_name(), key->db_name_length(), + key->name(), key->name_length()), + m_explicit_name(false) { - m_db.str= (char*)key->db_name(); - m_db.length= key->db_name_length(); - m_name.str= (char*)key->name(); - m_name.length= key->name_length(); - m_qname.str= qname_buff; if (m_db.length) - { strxmov(qname_buff, m_db.str, ".", m_name.str, NullS); - m_qname.length= m_db.length + 1 + m_name.length; - } else - { strmov(qname_buff, m_name.str); - m_qname.length= m_name.length; - } - m_explicit_name= false; -} - - -/** - Init the qualified name from the db and name. -*/ -void -sp_name::init_qname(THD *thd) -{ - const uint dot= !!m_db.length; - /* m_qname format: [database + dot] + name + '\0' */ - m_qname.length= m_db.length + dot + m_name.length; - if (!(m_qname.str= (char*) thd->alloc(m_qname.length + 1))) - return; - sprintf(m_qname.str, "%.*s%.*s%.*s", - (int) m_db.length, (m_db.length ? m_db.str : ""), - dot, ".", - (int) m_name.length, m_name.str); - DBUG_ASSERT(ok_for_lower_case_names(m_db.str)); } @@ -513,7 +484,7 @@ sp_name::init_qname(THD *thd) */ bool -check_routine_name(LEX_STRING *ident) +check_routine_name(const LEX_CSTRING *ident) { DBUG_ASSERT(ident); DBUG_ASSERT(ident->str); @@ -573,14 +544,33 @@ sp_head::operator delete(void *ptr, size_t size) throw() } -sp_head::sp_head() +sp_head::sp_head(const Sp_handler *sph) :Query_arena(&main_mem_root, STMT_INITIALIZED_FOR_SP), + Database_qualified_name(&null_clex_str, &null_clex_str), + m_handler(sph), m_flags(0), + m_tmp_query(NULL), + m_explicit_name(false), + /* + FIXME: the only use case when name is NULL is events, and it should + be rewritten soon. Remove the else part and replace 'if' with + an assert when this is done. + */ + m_qname(null_clex_str), + m_params(null_clex_str), + m_body(null_clex_str), + m_body_utf8(null_clex_str), + m_defstr(null_clex_str), m_sp_cache_version(0), m_creation_ctx(0), unsafe_flags(0), m_select_number(1), + m_created(0), + m_modified(0), m_recursion_level(0), m_next_cached_sp(0), + m_param_begin(NULL), + m_param_end(NULL), + m_body_begin(NULL), m_cont_level(0) { m_first_instance= this; @@ -588,25 +578,17 @@ sp_head::sp_head() m_last_cached_sp= this; m_return_field_def.charset = NULL; - /* - FIXME: the only use case when name is NULL is events, and it should - be rewritten soon. Remove the else part and replace 'if' with - an assert when this is done. - */ - m_db= m_name= m_qname= null_lex_str; DBUG_ENTER("sp_head::sp_head"); m_backpatch.empty(); + m_backpatch_goto.empty(); m_cont_backpatch.empty(); m_lex.empty(); my_hash_init(&m_sptabs, system_charset_info, 0, 0, 0, sp_table_key, 0, 0); my_hash_init(&m_sroutines, system_charset_info, 0, 0, 0, sp_sroutine_key, 0, 0); - m_body_utf8.str= NULL; - m_body_utf8.length= 0; - DBUG_VOID_RETURN; } @@ -628,39 +610,12 @@ sp_head::init(LEX *lex) lex->trg_table_fields.empty(); my_init_dynamic_array(&m_instr, sizeof(sp_instr *), 16, 8, MYF(0)); - m_param_begin= NULL; - m_param_end= NULL; - - m_body_begin= NULL ; - - m_qname.str= NULL; - m_qname.length= 0; - - m_explicit_name= false; - - m_db.str= NULL; - m_db.length= 0; - - m_name.str= NULL; - m_name.length= 0; - - m_params.str= NULL; - m_params.length= 0; - - m_body.str= NULL; - m_body.length= 0; - - m_defstr.str= NULL; - m_defstr.length= 0; - - m_return_field_def.charset= NULL; - DBUG_VOID_RETURN; } void -sp_head::init_sp_name(THD *thd, sp_name *spname) +sp_head::init_sp_name(const sp_name *spname) { DBUG_ENTER("sp_head::init_sp_name"); @@ -669,23 +624,10 @@ sp_head::init_sp_name(THD *thd, sp_name *spname) DBUG_ASSERT(spname && spname->m_db.str && spname->m_db.length); /* We have to copy strings to get them into the right memroot. */ - - m_db.length= spname->m_db.length; - m_db.str= strmake_root(thd->mem_root, spname->m_db.str, spname->m_db.length); - - m_name.length= spname->m_name.length; - m_name.str= strmake_root(thd->mem_root, spname->m_name.str, - spname->m_name.length); - + Database_qualified_name::copy(&main_mem_root, spname->m_db, spname->m_name); m_explicit_name= spname->m_explicit_name; - if (spname->m_qname.length == 0) - spname->init_qname(thd); - - m_qname.length= spname->m_qname.length; - m_qname.str= (char*) memdup_root(thd->mem_root, - spname->m_qname.str, - spname->m_qname.length + 1); + spname->make_qname(&main_mem_root, &m_qname); DBUG_VOID_RETURN; } @@ -743,58 +685,6 @@ sp_head::set_stmt_end(THD *thd) } -static TYPELIB * -create_typelib(MEM_ROOT *mem_root, Column_definition *field_def, List<String> *src) -{ - TYPELIB *result= NULL; - CHARSET_INFO *cs= field_def->charset; - DBUG_ENTER("create_typelib"); - - if (src->elements) - { - result= (TYPELIB*) alloc_root(mem_root, sizeof(TYPELIB)); - result->count= src->elements; - result->name= ""; - if (!(result->type_names=(const char **) - alloc_root(mem_root,(sizeof(char *)+sizeof(int))*(result->count+1)))) - DBUG_RETURN(0); - result->type_lengths= (uint*)(result->type_names + result->count+1); - List_iterator<String> it(*src); - String conv; - for (uint i=0; i < result->count; i++) - { - uint32 dummy; - uint length; - String *tmp= it++; - - if (String::needs_conversion(tmp->length(), tmp->charset(), - cs, &dummy)) - { - uint cnv_errs; - conv.copy(tmp->ptr(), tmp->length(), tmp->charset(), cs, &cnv_errs); - - length= conv.length(); - result->type_names[i]= (char*) strmake_root(mem_root, conv.ptr(), - length); - } - else - { - length= tmp->length(); - result->type_names[i]= strmake_root(mem_root, tmp->ptr(), length); - } - - // Strip trailing spaces. - length= cs->cset->lengthsp(cs, result->type_names[i], length); - result->type_lengths[i]= length; - ((uchar *)result->type_names[i])[length]= '\0'; - } - result->type_names[result->count]= 0; - result->type_lengths[result->count]= 0; - } - DBUG_RETURN(result); -} - - sp_head::~sp_head() { LEX *lex; @@ -840,10 +730,11 @@ sp_head::~sp_head() */ Field * -sp_head::create_result_field(uint field_max_length, const char *field_name, - TABLE *table) +sp_head::create_result_field(uint field_max_length, const LEX_CSTRING *field_name, + TABLE *table) const { Field *field; + LEX_CSTRING name; DBUG_ENTER("sp_head::create_result_field"); @@ -886,11 +777,13 @@ sp_head::create_result_field(uint field_max_length, const char *field_name, (m_return_field_def.pack_flag & (FIELDFLAG_BLOB|FIELDFLAG_GEOM)))); + if (field_name) + name= *field_name; + else + name= m_name; field= m_return_field_def.make_field(table->s, /* TABLE_SHARE ptr */ table->in_use->mem_root, - field_name ? - field_name : - (const char *) m_name.str); + &name); field->vcol_info= m_return_field_def.vcol_info; if (field) @@ -1065,30 +958,15 @@ subst_spvars(THD *thd, sp_instr *instr, LEX_STRING *query_str) } -/** - Return appropriate error about recursion limit reaching - - @param thd Thread handle - - @remark For functions and triggers we return error about - prohibited recursion. For stored procedures we - return about reaching recursion limit. -*/ - -void sp_head::recursion_level_error(THD *thd) +void Sp_handler_procedure::recursion_level_error(THD *thd, + const sp_head *sp) const { - if (m_type == TYPE_ENUM_PROCEDURE) - { - my_error(ER_SP_RECURSION_LIMIT, MYF(0), - static_cast<int>(thd->variables.max_sp_recursion_depth), - m_name.str); - } - else - my_error(ER_SP_NO_RECURSION, MYF(0)); + my_error(ER_SP_RECURSION_LIMIT, MYF(0), + static_cast<int>(thd->variables.max_sp_recursion_depth), + sp->m_name.str); } - /** Execute the routine. The main instruction jump loop is there. Assume the parameters already set. @@ -1120,7 +998,7 @@ sp_head::execute(THD *thd, bool merge_da_on_success) sp_rcontext *ctx= thd->spcont; bool err_status= FALSE; uint ip= 0; - ulonglong save_sql_mode; + sql_mode_t save_sql_mode; bool save_abort_on_warning; Query_arena *old_arena; /* per-instruction arena */ @@ -1277,11 +1155,10 @@ sp_head::execute(THD *thd, bool merge_da_on_success) /* Discard the initial part of executing routines. */ thd->profiling.discard_current_query(); #endif + sp_instr *i; DEBUG_SYNC(thd, "sp_head_execute_before_loop"); do { - sp_instr *i; - #if defined(ENABLED_PROFILING) /* Treat each "instr" of a routine as discrete unit that could be profiled. @@ -1441,6 +1318,13 @@ sp_head::execute(THD *thd, bool merge_da_on_success) da->opt_clear_warning_info(thd->query_id); da->copy_sql_conditions_from_wi(thd, &sp_wi); da->remove_marked_sql_conditions(); + if (i != NULL) + push_warning_printf(thd, Sql_condition::WARN_LEVEL_NOTE, + ER_SP_STACK_TRACE, + ER_THD(thd, ER_SP_STACK_TRACE), + i->m_lineno, + m_qname.str != NULL ? m_qname.str : + "anonymous block"); } } @@ -1462,7 +1346,7 @@ sp_head::execute(THD *thd, bool merge_da_on_success) NULL. In this case, mysql_change_db() would generate an error. */ - err_status|= mysql_change_db(thd, &saved_cur_db_name, TRUE); + err_status|= mysql_change_db(thd, (LEX_CSTRING*) &saved_cur_db_name, TRUE); } m_flags&= ~IS_INVOKED; DBUG_PRINT("info", @@ -1513,7 +1397,6 @@ sp_head::execute(THD *thd, bool merge_da_on_success) @param thd thread handle @param sp stored routine to change the context for - @param is_proc TRUE is procedure, FALSE if function @param save_ctx pointer to an old security context @todo @@ -1528,13 +1411,12 @@ sp_head::execute(THD *thd, bool merge_da_on_success) */ bool -set_routine_security_ctx(THD *thd, sp_head *sp, bool is_proc, - Security_context **save_ctx) +set_routine_security_ctx(THD *thd, sp_head *sp, Security_context **save_ctx) { *save_ctx= 0; - if (sp->m_chistics->suid != SP_IS_NOT_SUID && - sp->m_security_ctx.change_security_context(thd, &sp->m_definer_user, - &sp->m_definer_host, + if (sp->suid() != SP_IS_NOT_SUID && + sp->m_security_ctx.change_security_context(thd, &sp->m_definer.user, + &sp->m_definer.host, &sp->m_db, save_ctx)) return TRUE; @@ -1551,7 +1433,7 @@ set_routine_security_ctx(THD *thd, sp_head *sp, bool is_proc, */ if (*save_ctx && check_routine_access(thd, EXECUTE_ACL, - sp->m_db.str, sp->m_name.str, is_proc, FALSE)) + sp->m_db.str, sp->m_name.str, sp->m_handler, false)) { sp->m_security_ctx.restore_security_context(thd, *save_ctx); *save_ctx= 0; @@ -1563,6 +1445,73 @@ set_routine_security_ctx(THD *thd, sp_head *sp, bool is_proc, #endif // ! NO_EMBEDDED_ACCESS_CHECKS +bool sp_head::check_execute_access(THD *thd) const +{ + return check_routine_access(thd, EXECUTE_ACL, + m_db.str, m_name.str, + m_handler, false); +} + + +/** + Create rcontext optionally using the routine security. + This is important for sql_mode=ORACLE to make sure that the invoker has + access to the tables mentioned in the %TYPE references. + + In non-Oracle sql_modes we do not need access to any tables, + so we can omit the security context switch for performance purposes. + + @param thd + @param ret_value + @retval NULL - error (access denided or EOM) + @retval !NULL - success (the invoker has rights to all %TYPE tables) +*/ +sp_rcontext *sp_head::rcontext_create(THD *thd, Field *ret_value, + Row_definition_list *defs, + bool switch_security_ctx) +{ + if (!(m_flags & HAS_COLUMN_TYPE_REFS)) + return sp_rcontext::create(thd, this, m_pcont, ret_value, *defs); + sp_rcontext *res= NULL; +#ifndef NO_EMBEDDED_ACCESS_CHECKS + Security_context *save_security_ctx; + if (switch_security_ctx && + set_routine_security_ctx(thd, this, &save_security_ctx)) + return NULL; +#endif + if (!defs->resolve_type_refs(thd)) + res= sp_rcontext::create(thd, this, m_pcont, ret_value, *defs); +#ifndef NO_EMBEDDED_ACCESS_CHECKS + if (switch_security_ctx) + m_security_ctx.restore_security_context(thd, save_security_ctx); +#endif + return res; +} + + +sp_rcontext *sp_head::rcontext_create(THD *thd, Field *ret_value, + List<Item> *args) +{ + DBUG_ASSERT(args); + Row_definition_list defs; + m_pcont->retrieve_field_definitions(&defs); + if (defs.adjust_formal_params_to_actual_params(thd, args)) + return NULL; + return rcontext_create(thd, ret_value, &defs, true); +} + + +sp_rcontext *sp_head::rcontext_create(THD *thd, Field *ret_value, + Item **args, uint arg_count) +{ + Row_definition_list defs; + m_pcont->retrieve_field_definitions(&defs); + if (defs.adjust_formal_params_to_actual_params(thd, args, arg_count)) + return NULL; + return rcontext_create(thd, ret_value, &defs, true); +} + + /** Execute trigger stored program. @@ -1591,8 +1540,8 @@ set_routine_security_ctx(THD *thd, sp_head *sp, bool is_proc, bool sp_head::execute_trigger(THD *thd, - const LEX_STRING *db_name, - const LEX_STRING *table_name, + const LEX_CSTRING *db_name, + const LEX_CSTRING *table_name, GRANT_INFO *grant_info) { sp_rcontext *octx = thd->spcont; @@ -1609,10 +1558,10 @@ sp_head::execute_trigger(THD *thd, Security_context *save_ctx= NULL; - if (m_chistics->suid != SP_IS_NOT_SUID && + if (suid() != SP_IS_NOT_SUID && m_security_ctx.change_security_context(thd, - &m_definer_user, - &m_definer_host, + &m_definer.user, + &m_definer.host, &m_db, &save_ctx)) DBUG_RETURN(TRUE); @@ -1660,16 +1609,14 @@ sp_head::execute_trigger(THD *thd, init_sql_alloc(&call_mem_root, MEM_ROOT_BLOCK_SIZE, 0, MYF(0)); thd->set_n_backup_active_arena(&call_arena, &backup_arena); - if (!(nctx= sp_rcontext::create(thd, m_pcont, NULL))) + Row_definition_list defs; + m_pcont->retrieve_field_definitions(&defs); + if (!(nctx= rcontext_create(thd, NULL, &defs, false))) { err_status= TRUE; goto err_with_cleanup; } -#ifndef DBUG_OFF - nctx->sp= this; -#endif - thd->spcont= nctx; err_status= execute(thd, FALSE); @@ -1757,7 +1704,8 @@ sp_head::execute_function(THD *thd, Item **argp, uint argcount, invoking query properly. */ my_error(ER_SP_WRONG_NO_OF_ARGS, MYF(0), - "FUNCTION", m_qname.str, m_pcont->context_var_count(), argcount); + "FUNCTION", ErrConvDQName(this).ptr(), + m_pcont->context_var_count(), argcount); DBUG_RETURN(TRUE); } /* @@ -1774,7 +1722,7 @@ sp_head::execute_function(THD *thd, Item **argp, uint argcount, init_sql_alloc(&call_mem_root, MEM_ROOT_BLOCK_SIZE, 0, MYF(0)); thd->set_n_backup_active_arena(&call_arena, &backup_arena); - if (!(nctx= sp_rcontext::create(thd, m_pcont, return_value_fld))) + if (!(nctx= rcontext_create(thd, return_value_fld, argp, argcount))) { thd->restore_active_arena(&call_arena, &backup_arena); err_status= TRUE; @@ -1789,10 +1737,6 @@ sp_head::execute_function(THD *thd, Item **argp, uint argcount, */ thd->restore_active_arena(&call_arena, &backup_arena); -#ifndef DBUG_OFF - nctx->sp= this; -#endif - /* Pass arguments. */ for (arg_no= 0; arg_no < argcount; arg_no++) { @@ -1831,9 +1775,9 @@ sp_head::execute_function(THD *thd, Item **argp, uint argcount, if (arg_no) binlog_buf.append(','); - str_value= sp_get_item_value(thd, nctx->get_item(arg_no), - &str_value_holder); - + Item *item= nctx->get_item(arg_no); + str_value= item->type_handler()->print_item_value(thd, item, + &str_value_holder); if (str_value) binlog_buf.append(*str_value); else @@ -1845,7 +1789,7 @@ sp_head::execute_function(THD *thd, Item **argp, uint argcount, #ifndef NO_EMBEDDED_ACCESS_CHECKS Security_context *save_security_ctx; - if (set_routine_security_ctx(thd, this, FALSE, &save_security_ctx)) + if (set_routine_security_ctx(thd, this, &save_security_ctx)) { err_status= TRUE; goto err_with_cleanup; @@ -1981,7 +1925,7 @@ sp_head::execute_procedure(THD *thd, List<Item> *args) if (args->elements != params) { my_error(ER_SP_WRONG_NO_OF_ARGS, MYF(0), "PROCEDURE", - m_qname.str, params, args->elements); + ErrConvDQName(this).ptr(), params, args->elements); DBUG_RETURN(TRUE); } @@ -1989,30 +1933,24 @@ sp_head::execute_procedure(THD *thd, List<Item> *args) if (! octx) { /* Create a temporary old context. */ - if (!(octx= sp_rcontext::create(thd, m_pcont, NULL))) + if (!(octx= rcontext_create(thd, NULL, args))) { DBUG_PRINT("error", ("Could not create octx")); DBUG_RETURN(TRUE); } -#ifndef DBUG_OFF - octx->sp= 0; -#endif thd->spcont= octx; /* set callers_arena to thd, for upper-level function to work */ thd->spcont->callers_arena= thd; } - if (!(nctx= sp_rcontext::create(thd, m_pcont, NULL))) + if (!(nctx= rcontext_create(thd, NULL, args))) { delete nctx; /* Delete nctx if it was init() that failed. */ thd->spcont= save_spcont; DBUG_RETURN(TRUE); } -#ifndef DBUG_OFF - nctx->sp= this; -#endif if (params > 0) { @@ -2039,7 +1977,7 @@ sp_head::execute_procedure(THD *thd, List<Item> *args) if (!srp) { - my_error(ER_SP_NOT_VAR_ARG, MYF(0), i+1, m_qname.str); + my_error(ER_SP_NOT_VAR_ARG, MYF(0), i+1, ErrConvDQName(this).ptr()); err_status= TRUE; break; } @@ -2087,7 +2025,6 @@ sp_head::execute_procedure(THD *thd, List<Item> *args) thd->get_stmt_da()->set_overwrite_status(false); } - thd_proc_info(thd, "closing tables"); close_thread_tables(thd); thd_proc_info(thd, 0); @@ -2127,7 +2064,7 @@ sp_head::execute_procedure(THD *thd, List<Item> *args) #ifndef NO_EMBEDDED_ACCESS_CHECKS Security_context *save_security_ctx= 0; if (!err_status) - err_status= set_routine_security_ctx(thd, this, TRUE, &save_security_ctx); + err_status= set_routine_security_ctx(thd, this, &save_security_ctx); #endif if (!err_status) @@ -2183,8 +2120,8 @@ sp_head::execute_procedure(THD *thd, List<Item> *args) out_param_info->db_name= m_db.str; out_param_info->table_name= m_name.str; out_param_info->org_table_name= m_name.str; - out_param_info->col_name= spvar->name.str; - out_param_info->org_col_name= spvar->name.str; + out_param_info->col_name= spvar->name; + out_param_info->org_col_name= spvar->name; srp->set_out_param_info(out_param_info); } @@ -2228,34 +2165,23 @@ sp_head::execute_procedure(THD *thd, List<Item> *args) */ bool -sp_head::reset_lex(THD *thd) +sp_head::reset_lex(THD *thd, sp_lex_local *sublex) { DBUG_ENTER("sp_head::reset_lex"); - LEX *sublex; LEX *oldlex= thd->lex; - sublex= new (thd->mem_root)st_lex_local; - if (sublex == 0) - DBUG_RETURN(TRUE); - - thd->lex= sublex; - (void)m_lex.push_front(oldlex); + thd->set_local_lex(sublex); - /* Reset most stuff. */ - lex_start(thd); - - /* And keep the SP stuff too */ - sublex->sphead= oldlex->sphead; - sublex->spcont= oldlex->spcont; - /* And trigger related stuff too */ - sublex->trg_chistics= oldlex->trg_chistics; - sublex->trg_table_fields.empty(); - sublex->sp_lex_in_use= FALSE; + DBUG_RETURN(m_lex.push_front(oldlex)); +} - /* Reset part of parser state which needs this. */ - thd->m_parser_state->m_yacc.reset_before_substatement(); - DBUG_RETURN(FALSE); +bool +sp_head::reset_lex(THD *thd) +{ + DBUG_ENTER("sp_head::reset_lex"); + sp_lex_local *sublex= new (thd->mem_root) sp_lex_local(thd, thd->lex); + DBUG_RETURN(sublex ? reset_lex(thd, sublex) : true); } @@ -2263,6 +2189,8 @@ sp_head::reset_lex(THD *thd) Restore lex during parsing, after we have parsed a sub statement. @param thd Thread handle + @param oldlex The upper level lex we're near to restore to + @param sublex The local lex we're near to restore from @return @retval TRUE failure @@ -2270,23 +2198,17 @@ sp_head::reset_lex(THD *thd) */ bool -sp_head::restore_lex(THD *thd) +sp_head::merge_lex(THD *thd, LEX *oldlex, LEX *sublex) { - DBUG_ENTER("sp_head::restore_lex"); - LEX *sublex= thd->lex; - LEX *oldlex; + DBUG_ENTER("sp_head::merge_lex"); sublex->set_trg_event_type_for_tables(); - oldlex= (LEX *)m_lex.pop(); - if (! oldlex) - DBUG_RETURN(FALSE); // Nothing to restore - oldlex->trg_table_fields.push_back(&sublex->trg_table_fields); /* If this substatement is unsafe, the entire routine is too. */ - DBUG_PRINT("info", ("lex->get_stmt_unsafe_flags: 0x%x", - thd->lex->get_stmt_unsafe_flags())); + DBUG_PRINT("info", ("sublex->get_stmt_unsafe_flags: 0x%x", + sublex->get_stmt_unsafe_flags())); unsafe_flags|= sublex->get_stmt_unsafe_flags(); /* @@ -2308,13 +2230,6 @@ sp_head::restore_lex(THD *thd) /* Merge lists of PS parameters. */ oldlex->param_list.append(&sublex->param_list); - if (! sublex->sp_lex_in_use) - { - sublex->sphead= NULL; - lex_end(sublex); - delete sublex; - } - thd->lex= oldlex; DBUG_RETURN(FALSE); } @@ -2322,7 +2237,8 @@ sp_head::restore_lex(THD *thd) Put the instruction on the backpatch list, associated with the label. */ int -sp_head::push_backpatch(THD *thd, sp_instr *i, sp_label *lab) +sp_head::push_backpatch(THD *thd, sp_instr *i, sp_label *lab, + List<bp_t> *list, backpatch_instr_type itype) { bp_t *bp= (bp_t *) thd->alloc(sizeof(bp_t)); @@ -2330,7 +2246,45 @@ sp_head::push_backpatch(THD *thd, sp_instr *i, sp_label *lab) return 1; bp->lab= lab; bp->instr= i; - return m_backpatch.push_front(bp); + bp->instr_type= itype; + return list->push_front(bp); +} + +int +sp_head::push_backpatch(THD *thd, sp_instr *i, sp_label *lab) +{ + return push_backpatch(thd, i, lab, &m_backpatch, GOTO); +} + +int +sp_head::push_backpatch_goto(THD *thd, sp_pcontext *ctx, sp_label *lab) +{ + uint ip= instructions(); + + /* + Add cpop/hpop : they will be removed or updated later if target is in + the same block or not + */ + sp_instr_hpop *hpop= new (thd->mem_root) sp_instr_hpop(ip++, ctx, 0); + if (hpop == NULL || add_instr(hpop)) + return true; + if (push_backpatch(thd, hpop, lab, &m_backpatch_goto, HPOP)) + return true; + + sp_instr_cpop *cpop= new (thd->mem_root) sp_instr_cpop(ip++, ctx, 0); + if (cpop == NULL || add_instr(cpop)) + return true; + if (push_backpatch(thd, cpop, lab, &m_backpatch_goto, CPOP)) + return true; + + // Add jump with ip=0. IP will be updated when label is found. + sp_instr_jump *i= new (thd->mem_root) sp_instr_jump(ip, ctx); + if (i == NULL || add_instr(i)) + return true; + if (push_backpatch(thd, i, lab, &m_backpatch_goto, GOTO)) + return true; + + return false; } /** @@ -2357,44 +2311,98 @@ sp_head::backpatch(sp_label *lab) DBUG_VOID_RETURN; } -/** - Prepare an instance of Column_definition for field creation - (fill all necessary attributes). - - @param[in] thd Thread handle - @param[in] lex Yacc parsing context - @param[out] field_def An instance of create_field to be filled +void +sp_head::backpatch_goto(THD *thd, sp_label *lab,sp_label *lab_begin_block) +{ + bp_t *bp; + uint dest= instructions(); + List_iterator<bp_t> li(m_backpatch_goto); - @retval - FALSE on success - @retval - TRUE on error -*/ + DBUG_ENTER("sp_head::backpatch_goto"); + while ((bp= li++)) + { + if (bp->instr->m_ip < lab_begin_block->ip || bp->instr->m_ip > lab->ip) + { + /* + Update only jump target from the beginning of the block where the + label is defined. + */ + continue; + } + if (my_strcasecmp(system_charset_info, + bp->lab->name.str, + lab->name.str) == 0) + { + if (bp->instr_type == GOTO) + { + DBUG_PRINT("info", + ("backpatch_goto: (m_ip %d, label %p <%s>) to dest %d", + bp->instr->m_ip, lab, lab->name.str, dest)); + bp->instr->backpatch(dest, lab->ctx); + // Jump resolved, remove from the list + li.remove(); + continue; + } + if (bp->instr_type == CPOP) + { + int n= lab->ctx->diff_cursors(lab_begin_block->ctx, true); + if (n == 0) + { + // Remove cpop instr + replace_instr_to_nop(thd,bp->instr->m_ip); + } + else + { + // update count of cpop + static_cast<sp_instr_cpop*>(bp->instr)->update_count(n); + n= 1; + } + li.remove(); + continue; + } + if (bp->instr_type == HPOP) + { + int n= lab->ctx->diff_handlers(lab_begin_block->ctx, true); + if (n == 0) + { + // Remove hpop instr + replace_instr_to_nop(thd,bp->instr->m_ip); + } + else + { + // update count of cpop + static_cast<sp_instr_hpop*>(bp->instr)->update_count(n); + n= 1; + } + li.remove(); + continue; + } + } + } + DBUG_VOID_RETURN; +} bool -sp_head::fill_field_definition(THD *thd, LEX *lex, - Column_definition *field_def) +sp_head::check_unresolved_goto() { - uint unused1= 0; - - if (field_def->check(thd)) - return TRUE; - - if (field_def->interval_list.elements) - field_def->interval= create_typelib(mem_root, field_def, - &field_def->interval_list); - - sp_prepare_create_field(thd, field_def); - - if (prepare_create_field(field_def, &unused1, HA_CAN_GEOMETRY)) + DBUG_ENTER("sp_head::check_unresolved_goto"); + bool has_unresolved_label=false; + if (m_backpatch_goto.elements > 0) { - return TRUE; + List_iterator_fast<bp_t> li(m_backpatch_goto); + bp_t *bp; + while ((bp= li++)) + { + if ((bp->instr_type == GOTO)) + { + my_error(ER_SP_LILABEL_MISMATCH, MYF(0), "GOTO", bp->lab->name.str); + has_unresolved_label=true; + } + } } - - return FALSE; + DBUG_RETURN(has_unresolved_label); } - int sp_head::new_cont_backpatch(sp_instr_opt_meta *i) { @@ -2430,53 +2438,44 @@ sp_head::do_cont_backpatch() } } -void -sp_head::set_info(longlong created, longlong modified, - st_sp_chistics *chistics, sql_mode_t sql_mode) + +bool +sp_head::sp_add_instr_cpush_for_cursors(THD *thd, sp_pcontext *pcontext) { - m_created= created; - m_modified= modified; - m_chistics= (st_sp_chistics *) memdup_root(mem_root, (char*) chistics, - sizeof(*chistics)); - if (m_chistics->comment.length == 0) - m_chistics->comment.str= 0; - else - m_chistics->comment.str= strmake_root(mem_root, - m_chistics->comment.str, - m_chistics->comment.length); - m_sql_mode= sql_mode; + for (uint i= 0; i < pcontext->frame_cursor_count(); i++) + { + const sp_pcursor *c= pcontext->get_cursor_by_local_frame_offset(i); + sp_instr_cpush *instr= new (thd->mem_root) + sp_instr_cpush(instructions(), pcontext, c->lex(), + pcontext->cursor_offset() + i); + if (instr == NULL || add_instr(instr)) + return true; + } + return false; } void -sp_head::set_definer(const char *definer, uint definerlen) +sp_head::set_chistics(const st_sp_chistics &chistics) { - char user_name_holder[USERNAME_LENGTH + 1]; - LEX_STRING user_name= { user_name_holder, USERNAME_LENGTH }; - - char host_name_holder[HOSTNAME_LENGTH + 1]; - LEX_STRING host_name= { host_name_holder, HOSTNAME_LENGTH }; - - if (parse_user(definer, definerlen, user_name.str, &user_name.length, - host_name.str, &host_name.length) && - user_name.length && !host_name.length) - { - // 'user@' -> 'user@%' - host_name= host_not_specified; - } - - set_definer(&user_name, &host_name); + m_chistics.set(chistics); + if (m_chistics.comment.length == 0) + m_chistics.comment.str= 0; + else + m_chistics.comment.str= strmake_root(mem_root, + m_chistics.comment.str, + m_chistics.comment.length); } void -sp_head::set_definer(const LEX_STRING *user_name, const LEX_STRING *host_name) +sp_head::set_info(longlong created, longlong modified, + const st_sp_chistics &chistics, sql_mode_t sql_mode) { - m_definer_user.str= strmake_root(mem_root, user_name->str, user_name->length); - m_definer_user.length= user_name->length; - - m_definer_host.str= strmake_root(mem_root, host_name->str, host_name->length); - m_definer_host.length= host_name->length; + m_created= created; + m_modified= modified; + set_chistics(chistics); + m_sql_mode= sql_mode; } @@ -2549,20 +2548,20 @@ bool check_show_routine_access(THD *thd, sp_head *sp, bool *full_access) 1, TRUE) && (tables.grant.privilege & SELECT_ACL) != 0) || /* Check if user owns the routine. */ - (!strcmp(sp->m_definer_user.str, + (!strcmp(sp->m_definer.user.str, thd->security_ctx->priv_user) && - !strcmp(sp->m_definer_host.str, + !strcmp(sp->m_definer.host.str, thd->security_ctx->priv_host)) || /* Check if current role or any of the sub-granted roles own the routine. */ - (sp->m_definer_host.length == 0 && - (!strcmp(sp->m_definer_user.str, + (sp->m_definer.host.length == 0 && + (!strcmp(sp->m_definer.user.str, thd->security_ctx->priv_role) || check_role_is_granted(thd->security_ctx->priv_role, NULL, - sp->m_definer_user.str)))); + sp->m_definer.user.str)))); if (!*full_access) return check_some_routine_access(thd, sp->m_db.str, sp->m_name.str, - sp->m_type == TYPE_ENUM_PROCEDURE); + sp->m_handler); return 0; } @@ -2571,9 +2570,8 @@ bool check_show_routine_access(THD *thd, sp_head *sp, bool *full_access) Collect metadata for SHOW CREATE statement for stored routines. @param thd Thread context. - @param type Stored routine type - @param type Stored routine type - (TYPE_ENUM_PROCEDURE or TYPE_ENUM_FUNCTION) + @param sph Stored routine handler + @param fields Item list to populate @return Error status. @retval FALSE on success @@ -2581,13 +2579,11 @@ bool check_show_routine_access(THD *thd, sp_head *sp, bool *full_access) */ void -sp_head::show_create_routine_get_fields(THD *thd, int type, List<Item> *fields) +sp_head::show_create_routine_get_fields(THD *thd, const Sp_handler *sph, + List<Item> *fields) { - const char *col1_caption= type == TYPE_ENUM_PROCEDURE ? - "Procedure" : "Function"; - - const char *col3_caption= type == TYPE_ENUM_PROCEDURE ? - "Create Procedure" : "Create Function"; + const char *col1_caption= sph->show_create_routine_col1_caption(); + const char *col3_caption= sph->show_create_routine_col3_caption(); MEM_ROOT *mem_root= thd->mem_root; @@ -2634,8 +2630,7 @@ sp_head::show_create_routine_get_fields(THD *thd, int type, List<Item> *fields) Implement SHOW CREATE statement for stored routines. @param thd Thread context. - @param type Stored routine type - (TYPE_ENUM_PROCEDURE or TYPE_ENUM_FUNCTION) + @param sph Stored routine handler @return Error status. @retval FALSE on success @@ -2643,20 +2638,17 @@ sp_head::show_create_routine_get_fields(THD *thd, int type, List<Item> *fields) */ bool -sp_head::show_create_routine(THD *thd, int type) +sp_head::show_create_routine(THD *thd, const Sp_handler *sph) { - const char *col1_caption= type == TYPE_ENUM_PROCEDURE ? - "Procedure" : "Function"; - - const char *col3_caption= type == TYPE_ENUM_PROCEDURE ? - "Create Procedure" : "Create Function"; + const char *col1_caption= sph->show_create_routine_col1_caption(); + const char *col3_caption= sph->show_create_routine_col3_caption(); bool err_status; Protocol *protocol= thd->protocol; List<Item> fields; - LEX_STRING sql_mode; + LEX_CSTRING sql_mode; bool full_access; MEM_ROOT *mem_root= thd->mem_root; @@ -2664,9 +2656,6 @@ sp_head::show_create_routine(THD *thd, int type) DBUG_ENTER("sp_head::show_create_routine"); DBUG_PRINT("info", ("routine %s", m_name.str)); - DBUG_ASSERT(type == TYPE_ENUM_PROCEDURE || - type == TYPE_ENUM_FUNCTION); - if (check_show_routine_access(thd, this, &full_access)) DBUG_RETURN(TRUE); @@ -2761,10 +2750,89 @@ int sp_head::add_instr(sp_instr *instr) entire stored procedure, as their life span is equal. */ instr->mem_root= &main_mem_root; + instr->m_lineno= m_thd->m_parser_state->m_lip.yylineno; return insert_dynamic(&m_instr, (uchar*)&instr); } +bool sp_head::add_instr_jump(THD *thd, sp_pcontext *spcont) +{ + sp_instr_jump *i= new (thd->mem_root) sp_instr_jump(instructions(), spcont); + return i == NULL || add_instr(i); +} + + +bool sp_head::add_instr_jump(THD *thd, sp_pcontext *spcont, uint dest) +{ + sp_instr_jump *i= new (thd->mem_root) sp_instr_jump(instructions(), + spcont, dest); + return i == NULL || add_instr(i); +} + + +bool sp_head::add_instr_jump_forward_with_backpatch(THD *thd, + sp_pcontext *spcont, + sp_label *lab) +{ + sp_instr_jump *i= new (thd->mem_root) sp_instr_jump(instructions(), spcont); + if (i == NULL || add_instr(i)) + return true; + push_backpatch(thd, i, lab); + return false; +} + + +bool sp_head::add_instr_freturn(THD *thd, sp_pcontext *spcont, + Item *item, LEX *lex) +{ + sp_instr_freturn *i= new (thd->mem_root) + sp_instr_freturn(instructions(), spcont, item, + m_return_field_def.type_handler(), thd->lex); + if (i == NULL || add_instr(i)) + return true; + m_flags|= sp_head::HAS_RETURN; + return false; +} + + +bool sp_head::add_instr_preturn(THD *thd, sp_pcontext *spcont) +{ + sp_instr_preturn *i= new (thd->mem_root) + sp_instr_preturn(instructions(), spcont); + if (i == NULL || add_instr(i)) + return true; + return false; +} + + +/* + Replace an instruction at position to "no operation". + + @param thd - use mem_root of this THD for "new". + @param ip - position of the operation + @returns - true on error, false on success + + When we need to remove an instruction that during compilation + appeared to be useless (typically as useless jump), we replace + it to a jump to exactly the next instruction. + Such jumps are later removed during sp_head::optimize(). + + QQ: Perhaps we need a dedicated sp_instr_nop for this purpose. +*/ +bool sp_head::replace_instr_to_nop(THD *thd, uint ip) +{ + sp_instr *instr= get_instr(ip); + sp_instr_jump *nop= new (thd->mem_root) sp_instr_jump(instr->m_ip, + instr->m_ctx, + instr->m_ip + 1); + if (!nop) + return true; + delete instr; + set_dynamic(&m_instr, (uchar *) &nop, ip); + return false; +} + + /** Do some minimal optimization of the code: -# Mark used instructions @@ -2922,7 +2990,7 @@ sp_head::show_routine_code(THD *thd) push_warning(thd, Sql_condition::WARN_LEVEL_WARN, ER_UNKNOWN_ERROR, tmp); } protocol->prepare_for_resend(); - protocol->store((longlong)ip); + protocol->store_long(ip); buffer.set("", 0, system_charset_info); i->print(&buffer); @@ -3042,7 +3110,6 @@ sp_lex_keeper::reset_lex_and_exec_core(THD *thd, uint *nextp, thd->is_error() ? trans_rollback_stmt(thd) : trans_commit_stmt(thd); thd->get_stmt_da()->set_overwrite_status(false); } - thd_proc_info(thd, "closing tables"); close_thread_tables(thd); thd_proc_info(thd, 0); @@ -3112,6 +3179,25 @@ sp_lex_keeper::reset_lex_and_exec_core(THD *thd, uint *nextp, } +int sp_lex_keeper::cursor_reset_lex_and_exec_core(THD *thd, uint *nextp, + bool open_tables, + sp_instr *instr) +{ + Query_arena *old_arena= thd->stmt_arena; + /* + Get the Query_arena from the cursor statement LEX, which contains + the free_list of the query, so new items (if any) are stored in + the right free_list, and we can cleanup after each cursor operation, + e.g. open or cursor_copy_struct (for cursor%ROWTYPE variables). + */ + thd->stmt_arena= m_lex->query_arena(); + int res= reset_lex_and_exec_core(thd, nextp, open_tables, instr); + cleanup_items(thd->stmt_arena->free_list); + thd->stmt_arena= old_arena; + return res; +} + + /* sp_instr class functions */ @@ -3329,6 +3415,114 @@ sp_instr_set::print(String *str) /* + sp_instr_set_field class functions +*/ + +int +sp_instr_set_row_field::exec_core(THD *thd, uint *nextp) +{ + int res= thd->spcont->set_variable_row_field(thd, m_offset, m_field_offset, + &m_value); + if (res) + { + /* Failed to evaluate the value. Reset the variable to NULL. */ + thd->spcont->set_variable_row_field_to_null(thd, m_offset, m_field_offset); + } + delete_explain_query(thd->lex); + *nextp= m_ip + 1; + return res; +} + + +void +sp_instr_set_row_field::print(String *str) +{ + /* set name@offset[field_offset] ... */ + int rsrv= SP_INSTR_UINT_MAXLEN + 6 + 6 + 3; + sp_variable *var= m_ctx->find_variable(m_offset); + DBUG_ASSERT(var); + DBUG_ASSERT(var->field_def.is_row()); + const Column_definition *def= + var->field_def.row_field_definitions()->elem(m_field_offset); + DBUG_ASSERT(def); + + rsrv+= var->name.length + def->field_name.length; + if (str->reserve(rsrv)) + return; + str->qs_append(STRING_WITH_LEN("set ")); + str->qs_append(var->name.str, var->name.length); + str->qs_append('.'); + str->qs_append(def->field_name.str, def->field_name.length); + str->qs_append('@'); + str->qs_append(m_offset); + str->qs_append('['); + str->qs_append(m_field_offset); + str->qs_append(']'); + str->qs_append(' '); + m_value->print(str, enum_query_type(QT_ORDINARY | + QT_ITEM_ORIGINAL_FUNC_NULLIF)); +} + + +/* + sp_instr_set_field_by_name class functions +*/ + +int +sp_instr_set_row_field_by_name::exec_core(THD *thd, uint *nextp) +{ + int res; + uint idx; + Item_field_row *row= (Item_field_row*) thd->spcont->get_item(m_offset); + if ((res= row->element_index_by_name(&idx, m_field_name))) + { + sp_variable *var= m_ctx->find_variable(m_offset); + my_error(ER_ROW_VARIABLE_DOES_NOT_HAVE_FIELD, MYF(0), + var->name.str, m_field_name.str); + goto error; + } + res= thd->spcont->set_variable_row_field(thd, m_offset, idx, &m_value); + if (res) + { + /* Failed to evaluate the value. Reset the variable to NULL. */ + thd->spcont->set_variable_row_field_to_null(thd, m_offset, idx); + } +error: + delete_explain_query(thd->lex); + *nextp= m_ip + 1; + return res; +} + + +void +sp_instr_set_row_field_by_name::print(String *str) +{ + /* set name.field@offset["field"] ... */ + int rsrv= SP_INSTR_UINT_MAXLEN + 6 + 6 + 3 + 2; + sp_variable *var= m_ctx->find_variable(m_offset); + DBUG_ASSERT(var); + DBUG_ASSERT(var->field_def.is_table_rowtype_ref() || + var->field_def.is_cursor_rowtype_ref()); + + rsrv+= var->name.length + 2 * m_field_name.length; + if (str->reserve(rsrv)) + return; + str->qs_append(STRING_WITH_LEN("set ")); + str->qs_append(var->name.str, var->name.length); + str->qs_append('.'); + str->qs_append(m_field_name.str, m_field_name.length); + str->qs_append('@'); + str->qs_append(m_offset); + str->qs_append("[\"",2); + str->qs_append(m_field_name.str, m_field_name.length); + str->qs_append("\"]",2); + str->qs_append(' '); + m_value->print(str, enum_query_type(QT_ORDINARY | + QT_ITEM_ORIGINAL_FUNC_NULLIF)); +} + + +/* sp_instr_set_trigger_field class functions */ @@ -3553,8 +3747,21 @@ sp_instr_freturn::exec_core(THD *thd, uint *nextp) That means, Diagnostics Area should be clean before its execution. */ - Diagnostics_area *da= thd->get_stmt_da(); - da->clear_warning_info(da->warning_info_id()); + if (!(thd->variables.sql_mode & MODE_ORACLE)) + { + /* + Don't clean warnings in ORACLE mode, + as they are needed for SQLCODE and SQLERRM: + BEGIN + SELECT a INTO a FROM t1; + RETURN 'No exception ' || SQLCODE || ' ' || SQLERRM; + EXCEPTION WHEN NO_DATA_FOUND THEN + RETURN 'Exception ' || SQLCODE || ' ' || SQLERRM; + END; + */ + Diagnostics_area *da= thd->get_stmt_da(); + da->clear_warning_info(da->warning_info_id()); + } /* Change <next instruction pointer>, so that this will be the last @@ -3581,7 +3788,7 @@ sp_instr_freturn::print(String *str) if (str->reserve(1024+8+32)) // Add some for the expr. too return; str->qs_append(STRING_WITH_LEN("freturn ")); - str->qs_append((uint)m_type); + str->qs_append(m_type_handler->name().ptr()); str->qs_append(' '); m_value->print(str, enum_query_type(QT_ORDINARY | QT_ITEM_ORIGINAL_FUNC_NULLIF)); @@ -3755,7 +3962,7 @@ sp_instr_cpush::execute(THD *thd, uint *nextp) { DBUG_ENTER("sp_instr_cpush::execute"); - int ret= thd->spcont->push_cursor(thd, &m_lex_keeper, this); + int ret= thd->spcont->push_cursor(thd, &m_lex_keeper); *nextp= m_ip+1; @@ -3766,7 +3973,7 @@ sp_instr_cpush::execute(THD *thd, uint *nextp) void sp_instr_cpush::print(String *str) { - const LEX_STRING *cursor_name= m_ctx->find_cursor(m_cursor); + const LEX_CSTRING *cursor_name= m_ctx->find_cursor(m_cursor); /* cpush name@offset */ uint rsrv= SP_INSTR_UINT_MAXLEN+7; @@ -3835,19 +4042,7 @@ sp_instr_copen::execute(THD *thd, uint *nextp) else { sp_lex_keeper *lex_keeper= c->get_lex_keeper(); - Query_arena *old_arena= thd->stmt_arena; - - /* - Get the Query_arena from the cpush instruction, which contains - the free_list of the query, so new items (if any) are stored in - the right free_list, and we can cleanup after each open. - */ - thd->stmt_arena= c->get_instr(); - res= lex_keeper->reset_lex_and_exec_core(thd, nextp, FALSE, this); - /* Cleanup the query's items */ - if (thd->stmt_arena->free_list) - cleanup_items(thd->stmt_arena->free_list); - thd->stmt_arena= old_arena; + res= lex_keeper->cursor_reset_lex_and_exec_core(thd, nextp, FALSE, this); /* TODO: Assert here that we either have an error or a cursor */ } DBUG_RETURN(res); @@ -3866,7 +4061,7 @@ sp_instr_copen::exec_core(THD *thd, uint *nextp) void sp_instr_copen::print(String *str) { - const LEX_STRING *cursor_name= m_ctx->find_cursor(m_cursor); + const LEX_CSTRING *cursor_name= m_ctx->find_cursor(m_cursor); /* copen name@offset */ uint rsrv= SP_INSTR_UINT_MAXLEN+7; @@ -3908,7 +4103,7 @@ sp_instr_cclose::execute(THD *thd, uint *nextp) void sp_instr_cclose::print(String *str) { - const LEX_STRING *cursor_name= m_ctx->find_cursor(m_cursor); + const LEX_CSTRING *cursor_name= m_ctx->find_cursor(m_cursor); /* cclose name@offset */ uint rsrv= SP_INSTR_UINT_MAXLEN+8; @@ -3939,7 +4134,7 @@ sp_instr_cfetch::execute(THD *thd, uint *nextp) Query_arena backup_arena; DBUG_ENTER("sp_instr_cfetch::execute"); - res= c ? c->fetch(thd, &m_varlist) : -1; + res= c ? c->fetch(thd, &m_varlist, m_error_on_no_data) : -1; *nextp= m_ip+1; DBUG_RETURN(res); @@ -3951,7 +4146,7 @@ sp_instr_cfetch::print(String *str) { List_iterator_fast<sp_variable> li(m_varlist); sp_variable *pv; - const LEX_STRING *cursor_name= m_ctx->find_cursor(m_cursor); + const LEX_CSTRING *cursor_name= m_ctx->find_cursor(m_cursor); /* cfetch name@offset vars... */ uint rsrv= SP_INSTR_UINT_MAXLEN+8; @@ -3980,6 +4175,84 @@ sp_instr_cfetch::print(String *str) /* + sp_instr_cursor_copy_struct class functions +*/ + +/** + This methods processes cursor %ROWTYPE declarations, e.g.: + CURSOR cur IS SELECT * FROM t1; + rec cur%ROWTYPE; + and does the following: + - opens the cursor without copying data (materialization). + - copies the cursor structure to the associated %ROWTYPE variable. +*/ +int +sp_instr_cursor_copy_struct::exec_core(THD *thd, uint *nextp) +{ + DBUG_ENTER("sp_instr_cursor_copy_struct::exec_core"); + int ret= 0; + Item_field_row *row= (Item_field_row*) thd->spcont->get_item(m_var); + DBUG_ASSERT(row->type_handler() == &type_handler_row); + + /* + Copy structure only once. If the cursor%ROWTYPE variable is declared + inside a LOOP block, it gets its structure on the first loop interation + and remembers the structure for all consequent loop iterations. + It we recreated the structure on every iteration, we would get + potential memory leaks, and it would be less efficient. + */ + if (!row->arguments()) + { + sp_cursor tmp(thd, &m_lex_keeper); + // Open the cursor without copying data + if (!(ret= tmp.open_view_structure_only(thd))) + { + Row_definition_list defs; + if (!(ret= tmp.export_structure(thd, &defs))) + { + /* + Create row elements on the caller arena. + It's the same arena that was used during sp_rcontext::create(). + This puts cursor%ROWTYPE elements on the same mem_root + where explicit ROW elements and table%ROWTYPE reside. + */ + Query_arena current_arena; + thd->set_n_backup_active_arena(thd->spcont->callers_arena, ¤t_arena); + row->row_create_items(thd, &defs); + thd->restore_active_arena(thd->spcont->callers_arena, ¤t_arena); + } + tmp.close(thd); + } + } + *nextp= m_ip + 1; + DBUG_RETURN(ret); +} + + +int +sp_instr_cursor_copy_struct::execute(THD *thd, uint *nextp) +{ + DBUG_ENTER("sp_instr_cursor_copy_struct::execute"); + int ret= m_lex_keeper.cursor_reset_lex_and_exec_core(thd, nextp, FALSE, this); + DBUG_RETURN(ret); +} + + +void +sp_instr_cursor_copy_struct::print(String *str) +{ + sp_variable *var= m_ctx->find_variable(m_var); + const LEX_CSTRING *name= m_lex_keeper.cursor_name(); + str->append(STRING_WITH_LEN("cursor_copy_struct ")); + str->append(name); + str->append(' '); + str->append(&var->name); + str->append('@'); + str->append_ulonglong(m_var); +} + + +/* sp_instr_error class functions */ @@ -4143,7 +4416,8 @@ sp_head::merge_table_list(THD *thd, TABLE_LIST *table, LEX *lex_for_tmp_check) { SP_TABLE *tab; - if (lex_for_tmp_check->sql_command == SQLCOM_DROP_TABLE && + if ((lex_for_tmp_check->sql_command == SQLCOM_DROP_TABLE || + lex_for_tmp_check->sql_command == SQLCOM_DROP_SEQUENCE) && lex_for_tmp_check->tmp_table()) return TRUE; @@ -4207,7 +4481,8 @@ sp_head::merge_table_list(THD *thd, TABLE_LIST *table, LEX *lex_for_tmp_check) { if (!(tab= (SP_TABLE *)thd->calloc(sizeof(SP_TABLE)))) return FALSE; - if (lex_for_tmp_check->sql_command == SQLCOM_CREATE_TABLE && + if ((lex_for_tmp_check->sql_command == SQLCOM_CREATE_TABLE || + lex_for_tmp_check->sql_command == SQLCOM_CREATE_SEQUENCE) && lex_for_tmp_check->query_tables == table && lex_for_tmp_check->tmp_table()) { @@ -4338,3 +4613,220 @@ sp_add_to_query_tables(THD *thd, LEX *lex, lex->add_to_query_tables(table); return table; } + + +Item *sp_head::adjust_assignment_source(THD *thd, Item *val, Item *val2) +{ + return val ? val : val2 ? val2 : new (thd->mem_root) Item_null(thd); +} + +/** + Helper action for a SET statement. + Used to push a SP local variable into the assignment list. + + @param var_type the SP local variable + @param val the value being assigned to the variable + + @return TRUE if error, FALSE otherwise. +*/ + +bool +sp_head::set_local_variable(THD *thd, sp_pcontext *spcont, + sp_variable *spv, Item *val, LEX *lex, + bool responsible_to_free_lex) +{ + if (!(val= adjust_assignment_source(thd, val, spv->default_value))) + return true; + + sp_instr_set *sp_set= new (thd->mem_root) + sp_instr_set(instructions(), spcont, + spv->offset, val, lex, + responsible_to_free_lex); + + return sp_set == NULL || add_instr(sp_set); +} + + +/** + Similar to set_local_variable(), but for ROW variable fields. +*/ +bool +sp_head::set_local_variable_row_field(THD *thd, sp_pcontext *spcont, + sp_variable *spv, uint field_idx, + Item *val, LEX *lex) +{ + if (!(val= adjust_assignment_source(thd, val, NULL))) + return true; + + sp_instr_set_row_field *sp_set= new (thd->mem_root) + sp_instr_set_row_field(instructions(), + spcont, + spv->offset, + field_idx, val, + lex, true); + return sp_set == NULL || add_instr(sp_set); +} + + +bool +sp_head::set_local_variable_row_field_by_name(THD *thd, sp_pcontext *spcont, + sp_variable *spv, + const LEX_CSTRING *field_name, + Item *val, LEX *lex) +{ + if (!(val= adjust_assignment_source(thd, val, NULL))) + return true; + + sp_instr_set_row_field_by_name *sp_set= + new (thd->mem_root) sp_instr_set_row_field_by_name(instructions(), + spcont, + spv->offset, + *field_name, + val, + lex, true); + return sp_set == NULL || add_instr(sp_set); +} + + +bool sp_head::add_open_cursor(THD *thd, sp_pcontext *spcont, uint offset, + sp_pcontext *param_spcont, + List<sp_assignment_lex> *parameters) +{ + /* + The caller must make sure that the number of formal parameters matches + the number of actual parameters. + */ + DBUG_ASSERT((param_spcont ? param_spcont->context_var_count() : 0) == + (parameters ? parameters->elements : 0)); + + if (parameters && + add_set_cursor_param_variables(thd, param_spcont, parameters)) + return true; + + sp_instr_copen *i= new (thd->mem_root) + sp_instr_copen(instructions(), spcont, offset); + return i == NULL || add_instr(i); +} + + +bool sp_head::add_for_loop_open_cursor(THD *thd, sp_pcontext *spcont, + sp_variable *index, + const sp_pcursor *pcursor, uint coffset, + sp_assignment_lex *param_lex, + Item_args *parameters) +{ + if (parameters && + add_set_for_loop_cursor_param_variables(thd, pcursor->param_context(), + param_lex, parameters)) + return true; + + sp_instr *instr_copy_struct= + new (thd->mem_root) sp_instr_cursor_copy_struct(instructions(), + spcont, pcursor->lex(), + index->offset); + if (instr_copy_struct == NULL || add_instr(instr_copy_struct)) + return true; + + sp_instr_copen *instr_copen= + new (thd->mem_root) sp_instr_copen(instructions(), spcont, coffset); + if (instr_copen == NULL || add_instr(instr_copen)) + return true; + + sp_instr_cfetch *instr_cfetch= + new (thd->mem_root) sp_instr_cfetch(instructions(), + spcont, coffset, false); + if (instr_cfetch == NULL || add_instr(instr_cfetch)) + return true; + instr_cfetch->add_to_varlist(index); + return false; +} + + +bool +sp_head::add_set_for_loop_cursor_param_variables(THD *thd, + sp_pcontext *param_spcont, + sp_assignment_lex *param_lex, + Item_args *parameters) +{ + DBUG_ASSERT(param_spcont->context_var_count() == parameters->argument_count()); + for (uint idx= 0; idx < parameters->argument_count(); idx ++) + { + /* + param_lex is shared between multiple items (cursor parameters). + Only the last sp_instr_set is responsible for freeing param_lex. + See more comments in LEX::sp_for_loop_cursor_declarations in sql_lex.cc. + */ + bool last= idx + 1 == parameters->argument_count(); + sp_variable *spvar= param_spcont->get_context_variable(idx); + if (set_local_variable(thd, param_spcont, + spvar, parameters->arguments()[idx], + param_lex, last)) + return true; + } + return false; +} + + +bool sp_head::spvar_fill_row(THD *thd, + sp_variable *spvar, + Row_definition_list *defs) +{ + spvar->field_def.set_row_field_definitions(defs); + spvar->field_def.field_name= spvar->name; + if (fill_spvar_definition(thd, &spvar->field_def)) + return true; + row_fill_field_definitions(thd, defs); + return false; +} + + +bool sp_head::spvar_fill_type_reference(THD *thd, + sp_variable *spvar, + const LEX_CSTRING &table, + const LEX_CSTRING &col) +{ + Qualified_column_ident *ref; + if (!(ref= new (thd->mem_root) Qualified_column_ident(&table, &col))) + return true; + fill_spvar_using_type_reference(spvar, ref); + return false; +} + + +bool sp_head::spvar_fill_type_reference(THD *thd, + sp_variable *spvar, + const LEX_CSTRING &db, + const LEX_CSTRING &table, + const LEX_CSTRING &col) +{ + Qualified_column_ident *ref; + if (!(ref= new (thd->mem_root) Qualified_column_ident(thd, &db, &table, &col))) + return true; + fill_spvar_using_type_reference(spvar, ref); + return false; +} + + +bool sp_head::spvar_fill_table_rowtype_reference(THD *thd, + sp_variable *spvar, + const LEX_CSTRING &table) +{ + Table_ident *ref; + if (!(ref= new (thd->mem_root) Table_ident(&table))) + return true; + fill_spvar_using_table_rowtype_reference(thd, spvar, ref); + return false; +} + + +bool sp_head::spvar_fill_table_rowtype_reference(THD *thd, + sp_variable *spvar, + const LEX_CSTRING &db, + const LEX_CSTRING &table) +{ + Table_ident *ref; + if (!(ref= new (thd->mem_root) Table_ident(thd, &db, &table, false))) + return true; + fill_spvar_using_table_rowtype_reference(thd, spvar, ref); + return false; +} |