diff options
Diffstat (limited to 'sql/sp_rcontext.cc')
-rw-r--r-- | sql/sp_rcontext.cc | 909 |
1 files changed, 909 insertions, 0 deletions
diff --git a/sql/sp_rcontext.cc b/sql/sp_rcontext.cc new file mode 100644 index 00000000..d2fe53a2 --- /dev/null +++ b/sql/sp_rcontext.cc @@ -0,0 +1,909 @@ +/* Copyright (c) 2002, 2010, Oracle and/or its affiliates. All rights reserved. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1335 USA */ + +#include "mariadb.h" +#include "sql_priv.h" +#include "unireg.h" +#ifdef USE_PRAGMA_IMPLEMENTATION +#pragma implementation +#endif + +#include "mysql.h" +#include "sp_head.h" +#include "sql_cursor.h" +#include "sp_rcontext.h" +#include "sp_pcontext.h" +#include "sql_select.h" // create_virtual_tmp_table +#include "sql_base.h" // open_tables_only_view_structure +#include "sql_acl.h" // SELECT_ACL +#include "sql_parse.h" // check_table_access + + +Sp_rcontext_handler_local sp_rcontext_handler_local; +Sp_rcontext_handler_package_body sp_rcontext_handler_package_body; + +sp_rcontext *Sp_rcontext_handler_local::get_rcontext(sp_rcontext *ctx) const +{ + return ctx; +} + +sp_rcontext *Sp_rcontext_handler_package_body::get_rcontext(sp_rcontext *ctx) const +{ + return ctx->m_sp->m_parent->m_rcontext; +} + +const LEX_CSTRING *Sp_rcontext_handler_local::get_name_prefix() const +{ + return &empty_clex_str; +} + +const LEX_CSTRING *Sp_rcontext_handler_package_body::get_name_prefix() const +{ + static const LEX_CSTRING sp_package_body_variable_prefix_clex_str= + {STRING_WITH_LEN("PACKAGE_BODY.")}; + return &sp_package_body_variable_prefix_clex_str; +} + + +/////////////////////////////////////////////////////////////////////////// +// sp_rcontext implementation. +/////////////////////////////////////////////////////////////////////////// + + +sp_rcontext::sp_rcontext(const sp_head *owner, + const sp_pcontext *root_parsing_ctx, + Field *return_value_fld, + bool in_sub_stmt) + :end_partial_result_set(false), + pause_state(false), quit_func(false), instr_ptr(0), + m_sp(owner), + m_root_parsing_ctx(root_parsing_ctx), + m_var_table(NULL), + m_return_value_fld(return_value_fld), + m_return_value_set(false), + m_in_sub_stmt(in_sub_stmt), + m_handlers(PSI_INSTRUMENT_MEM), m_handler_call_stack(PSI_INSTRUMENT_MEM), + m_ccount(0) +{ +} + + +sp_rcontext::~sp_rcontext() +{ + delete m_var_table; + // Leave m_handlers, m_handler_call_stack, m_var_items, m_cstack + // and m_case_expr_holders untouched. + // They are allocated in mem roots and will be freed accordingly. +} + + +sp_rcontext *sp_rcontext::create(THD *thd, + const sp_head *owner, + const sp_pcontext *root_parsing_ctx, + Field *return_value_fld, + Row_definition_list &field_def_lst) +{ + SELECT_LEX *save_current_select; + sp_rcontext *ctx= new (thd->mem_root) sp_rcontext(owner, + root_parsing_ctx, + return_value_fld, + thd->in_sub_stmt); + if (!ctx) + return NULL; + + /* Reset current_select as it's checked in Item_ident::Item_ident */ + save_current_select= thd->lex->current_select; + thd->lex->current_select= 0; + + if (ctx->alloc_arrays(thd) || + ctx->init_var_table(thd, field_def_lst) || + ctx->init_var_items(thd, field_def_lst)) + { + delete ctx; + ctx= 0; + } + + thd->lex->current_select= save_current_select; + return ctx; +} + + +bool Row_definition_list::append_uniq(MEM_ROOT *mem_root, Spvar_definition *var) +{ + DBUG_ASSERT(elements); + uint unused; + if (unlikely(find_row_field_by_name(&var->field_name, &unused))) + { + my_error(ER_DUP_FIELDNAME, MYF(0), var->field_name.str); + return true; + } + return push_back(var, mem_root); +} + + +bool Row_definition_list:: + adjust_formal_params_to_actual_params(THD *thd, List<Item> *args) +{ + List_iterator<Spvar_definition> it(*this); + List_iterator<Item> it_args(*args); + DBUG_ASSERT(elements >= args->elements ); + Spvar_definition *def; + Item *arg; + while ((def= it++) && (arg= it_args++)) + { + if (def->type_handler()->adjust_spparam_type(def, arg)) + return true; + } + return false; +} + + +bool Row_definition_list:: + adjust_formal_params_to_actual_params(THD *thd, + Item **args, uint arg_count) +{ + List_iterator<Spvar_definition> it(*this); + DBUG_ASSERT(elements >= arg_count ); + Spvar_definition *def; + for (uint i= 0; (def= it++) && (i < arg_count) ; i++) + { + if (def->type_handler()->adjust_spparam_type(def, args[i])) + return true; + } + return false; +} + + +bool sp_rcontext::alloc_arrays(THD *thd) +{ + { + size_t n= m_root_parsing_ctx->max_cursor_index(); + m_cstack.reset( + static_cast<sp_cursor **> ( + thd->alloc(n * sizeof (sp_cursor*))), + n); + } + + { + size_t n= m_root_parsing_ctx->get_num_case_exprs(); + m_case_expr_holders.reset( + static_cast<Item_cache **> ( + thd->calloc(n * sizeof (Item_cache*))), + n); + } + + return !m_cstack.array() || !m_case_expr_holders.array(); +} + + +bool sp_rcontext::init_var_table(THD *thd, + List<Spvar_definition> &field_def_lst) +{ + if (!m_root_parsing_ctx->max_var_index()) + return false; + + DBUG_ASSERT(field_def_lst.elements == m_root_parsing_ctx->max_var_index()); + + if (!(m_var_table= create_virtual_tmp_table(thd, field_def_lst))) + return true; + + return false; +} + + +/** + Check if we have access to use a column as a %TYPE reference. + @return false - OK + @return true - access denied +*/ +static inline bool +check_column_grant_for_type_ref(THD *thd, TABLE_LIST *table_list, + const char *str, size_t length, + Field *fld) +{ +#ifndef NO_EMBEDDED_ACCESS_CHECKS + table_list->table->grant.want_privilege= SELECT_ACL; + return check_column_grant_in_table_ref(thd, table_list, str, length, fld); +#else + return false; +#endif +} + + +/** + This method implementation is very close to fill_schema_table_by_open(). +*/ +bool Qualified_column_ident::resolve_type_ref(THD *thd, Column_definition *def) +{ + Open_tables_backup open_tables_state_backup; + thd->reset_n_backup_open_tables_state(&open_tables_state_backup); + + TABLE_LIST *table_list; + Field *src; + LEX *save_lex= thd->lex; + bool rc= true; + + sp_lex_local lex(thd, thd->lex); + thd->lex= &lex; + + lex.context_analysis_only= CONTEXT_ANALYSIS_ONLY_VIEW; + // Make %TYPE variables see temporary tables that shadow permanent tables + thd->temporary_tables= open_tables_state_backup.temporary_tables; + + if ((table_list= + lex.first_select_lex()->add_table_to_list(thd, this, NULL, 0, + TL_READ_NO_INSERT, + MDL_SHARED_READ)) && + !check_table_access(thd, SELECT_ACL, table_list, TRUE, UINT_MAX, FALSE) && + !open_tables_only_view_structure(thd, table_list, + thd->mdl_context.has_locks())) + { + if (likely((src= lex.query_tables->table->find_field_by_name(&m_column)))) + { + if (!(rc= check_column_grant_for_type_ref(thd, table_list, + m_column.str, + m_column.length, src))) + { + *def= Column_definition(thd, src, NULL/*No defaults,no constraints*/); + def->flags&= (uint) ~NOT_NULL_FLAG; + rc= def->sp_prepare_create_field(thd, thd->mem_root); + } + } + else + my_error(ER_BAD_FIELD_ERROR, MYF(0), m_column.str, table.str); + } + + lex.unit.cleanup(); + thd->temporary_tables= NULL; // Avoid closing temporary tables + close_thread_tables(thd); + thd->lex= save_lex; + thd->restore_backup_open_tables_state(&open_tables_state_backup); + return rc; +} + + +/** + This method resolves the structure of a variable declared as: + rec t1%ROWTYPE; + It opens the table "t1" and copies its structure to %ROWTYPE variable. +*/ +bool Table_ident::resolve_table_rowtype_ref(THD *thd, + Row_definition_list &defs) +{ + Open_tables_backup open_tables_state_backup; + thd->reset_n_backup_open_tables_state(&open_tables_state_backup); + + TABLE_LIST *table_list; + LEX *save_lex= thd->lex; + bool rc= true; + + /* + Create a temporary LEX on stack and switch to it. + In case of VIEW, open_tables_only_view_structure() will open more + tables/views recursively. We want to avoid them to stick to the current LEX. + */ + sp_lex_local lex(thd, thd->lex); + thd->lex= &lex; + + lex.context_analysis_only= CONTEXT_ANALYSIS_ONLY_VIEW; + // Make %ROWTYPE variables see temporary tables that shadow permanent tables + thd->temporary_tables= open_tables_state_backup.temporary_tables; + + if ((table_list= + lex.first_select_lex()->add_table_to_list(thd, this, NULL, 0, + TL_READ_NO_INSERT, + MDL_SHARED_READ)) && + !check_table_access(thd, SELECT_ACL, table_list, TRUE, UINT_MAX, FALSE) && + !open_tables_only_view_structure(thd, table_list, + thd->mdl_context.has_locks())) + { + for (Field **src= lex.query_tables->table->field; *src; src++) + { + /* + Make field names on the THD memory root, + as the table will be closed and freed soon, + in the end of this method. + */ + LEX_CSTRING tmp= src[0]->field_name; + Spvar_definition *def; + if ((rc= check_column_grant_for_type_ref(thd, table_list, + tmp.str, tmp.length,src[0])) || + (rc= !(src[0]->field_name.str= thd->strmake(tmp.str, tmp.length))) || + (rc= !(def= new (thd->mem_root) Spvar_definition(thd, *src)))) + break; + src[0]->field_name.str= tmp.str; // Restore field name, just in case. + def->flags&= (uint) ~NOT_NULL_FLAG; + if ((rc= def->sp_prepare_create_field(thd, thd->mem_root))) + break; + defs.push_back(def, thd->mem_root); + } + } + + lex.unit.cleanup(); + thd->temporary_tables= NULL; // Avoid closing temporary tables + close_thread_tables(thd); + thd->lex= save_lex; + thd->restore_backup_open_tables_state(&open_tables_state_backup); + return rc; +} + + +bool Row_definition_list::resolve_type_refs(THD *thd) +{ + List_iterator<Spvar_definition> it(*this); + Spvar_definition *def; + while ((def= it++)) + { + if (def->is_column_type_ref() && + def->column_type_ref()->resolve_type_ref(thd, def)) + return true; + } + return false; +}; + + +bool sp_rcontext::init_var_items(THD *thd, + List<Spvar_definition> &field_def_lst) +{ + uint num_vars= m_root_parsing_ctx->max_var_index(); + + m_var_items.reset( + static_cast<Item_field **> ( + thd->alloc(num_vars * sizeof (Item *))), + num_vars); + + if (!m_var_items.array()) + return true; + + DBUG_ASSERT(field_def_lst.elements == num_vars); + List_iterator<Spvar_definition> it(field_def_lst); + Spvar_definition *def= it++; + + for (uint idx= 0; idx < num_vars; ++idx, def= it++) + { + Field *field= m_var_table->field[idx]; + if (def->is_table_rowtype_ref()) + { + Row_definition_list defs; + Item_field_row *item= new (thd->mem_root) Item_field_row(thd, field); + if (!(m_var_items[idx]= item) || + def->table_rowtype_ref()->resolve_table_rowtype_ref(thd, defs) || + item->row_create_items(thd, &defs)) + return true; + } + else if (def->is_cursor_rowtype_ref()) + { + Row_definition_list defs; + Item_field_row *item= new (thd->mem_root) Item_field_row(thd, field); + if (!(m_var_items[idx]= item)) + return true; + } + else if (def->is_row()) + { + Item_field_row *item= new (thd->mem_root) Item_field_row(thd, field); + if (!(m_var_items[idx]= item) || + item->row_create_items(thd, def->row_field_definitions())) + return true; + } + else + { + if (!(m_var_items[idx]= new (thd->mem_root) Item_field(thd, field))) + return true; + } + } + return false; +} + + +bool Item_field_row::row_create_items(THD *thd, List<Spvar_definition> *list) +{ + DBUG_ASSERT(list); + DBUG_ASSERT(field); + Virtual_tmp_table **ptable= field->virtual_tmp_table_addr(); + DBUG_ASSERT(ptable); + if (!(ptable[0]= create_virtual_tmp_table(thd, *list))) + return true; + + if (alloc_arguments(thd, list->elements)) + return true; + + List_iterator<Spvar_definition> it(*list); + Spvar_definition *def; + for (arg_count= 0; (def= it++); arg_count++) + { + if (!(args[arg_count]= new (thd->mem_root) + Item_field(thd, ptable[0]->field[arg_count]))) + return true; + } + return false; +} + + +bool sp_rcontext::set_return_value(THD *thd, Item **return_value_item) +{ + DBUG_ASSERT(m_return_value_fld); + + m_return_value_set = true; + + return thd->sp_eval_expr(m_return_value_fld, return_value_item); +} + + +void sp_rcontext::push_cursor(sp_cursor *c) +{ + m_cstack[m_ccount++]= c; +} + + +void sp_rcontext::pop_cursor(THD *thd) +{ + DBUG_ASSERT(m_ccount > 0); + if (m_cstack[m_ccount - 1]->is_open()) + m_cstack[m_ccount - 1]->close(thd); + m_ccount--; +} + + +void sp_rcontext::pop_cursors(THD *thd, size_t count) +{ + DBUG_ASSERT(m_ccount >= count); + while (count--) + pop_cursor(thd); +} + + +bool sp_rcontext::push_handler(sp_instr_hpush_jump *entry) +{ + return m_handlers.append(entry); +} + + +void sp_rcontext::pop_handlers(size_t count) +{ + DBUG_ASSERT(m_handlers.elements() >= count); + + for (size_t i= 0; i < count; ++i) + m_handlers.pop(); +} + + +bool sp_rcontext::handle_sql_condition(THD *thd, + uint *ip, + const sp_instr *cur_spi) +{ + DBUG_ENTER("sp_rcontext::handle_sql_condition"); + + /* + If this is a fatal sub-statement error, and this runtime + context corresponds to a sub-statement, no CONTINUE/EXIT + handlers from this context are applicable: try to locate one + in the outer scope. + */ + if (unlikely(thd->is_fatal_sub_stmt_error) && m_in_sub_stmt) + DBUG_RETURN(false); + + Diagnostics_area *da= thd->get_stmt_da(); + const sp_handler *found_handler= NULL; + const Sql_condition *found_condition= NULL; + + if (unlikely(thd->is_error())) + { + found_handler= + cur_spi->m_ctx->find_handler(da->get_error_condition_identity()); + + if (found_handler) + found_condition= da->get_error_condition(); + + /* + Found condition can be NULL if the diagnostics area was full + when the error was raised. It can also be NULL if + Diagnostics_area::set_error_status(uint sql_error) was used. + In these cases, make a temporary Sql_condition here so the + error can be handled. + */ + if (!found_condition) + { + found_condition= + new (callers_arena->mem_root) Sql_condition(callers_arena->mem_root, + da->get_error_condition_identity(), + da->message(), + da->current_row_for_warning()); + } + } + else if (da->current_statement_warn_count()) + { + Diagnostics_area::Sql_condition_iterator it= da->sql_conditions(); + const Sql_condition *c; + + // Here we need to find the last warning/note from the stack. + // In MySQL most substantial warning is the last one. + // (We could have used a reverse iterator here if one existed) + + while ((c= it++)) + { + if (c->get_level() == Sql_condition::WARN_LEVEL_WARN || + c->get_level() == Sql_condition::WARN_LEVEL_NOTE) + { + const sp_handler *handler= cur_spi->m_ctx->find_handler(*c); + if (handler) + { + found_handler= handler; + found_condition= c; + } + } + } + } + + if (!found_handler) + DBUG_RETURN(false); + + // At this point, we know that: + // - there is a pending SQL-condition (error or warning); + // - there is an SQL-handler for it. + + DBUG_ASSERT(found_condition); + + sp_instr_hpush_jump *handler_entry= NULL; + for (size_t i= 0; i < m_handlers.elements(); ++i) + { + sp_instr_hpush_jump *h= m_handlers.at(i); + + if (h->get_handler() == found_handler) + { + handler_entry= h; + break; + } + } + + /* + handler_entry usually should not be NULL here, as that indicates + that the parser context thinks a HANDLER should be activated, + but the runtime context cannot find it. + + However, this can happen (and this is in line with the Standard) + if SQL-condition has been raised before DECLARE HANDLER instruction + is processed. + + For example: + CREATE PROCEDURE p() + BEGIN + DECLARE v INT DEFAULT 'get'; -- raises SQL-warning here + DECLARE EXIT HANDLER ... -- this handler does not catch the warning + END + */ + if (!handler_entry) + DBUG_RETURN(false); + + // Mark active conditions so that they can be deleted when the handler exits. + da->mark_sql_conditions_for_removal(); + + uint continue_ip= handler_entry->get_handler()->type == sp_handler::CONTINUE ? + cur_spi->get_cont_dest() : 0; + + /* End aborted result set. */ + if (end_partial_result_set) + thd->protocol->end_partial_result_set(thd); + + /* Reset error state. */ + thd->clear_error(); + thd->reset_killed(); // Some errors set thd->killed, (e.g. "bad data"). + + /* Add a frame to handler-call-stack. */ + Sql_condition_info *cond_info= + new (callers_arena->mem_root) Sql_condition_info(found_condition, + callers_arena); + Handler_call_frame *frame= + new (callers_arena->mem_root) Handler_call_frame(cond_info, continue_ip); + m_handler_call_stack.append(frame); + + *ip= handler_entry->m_ip + 1; + + DBUG_RETURN(true); +} + + +uint sp_rcontext::exit_handler(Diagnostics_area *da) +{ + DBUG_ENTER("sp_rcontext::exit_handler"); + DBUG_ASSERT(m_handler_call_stack.elements() > 0); + + Handler_call_frame *f= m_handler_call_stack.pop(); + + /* + Remove the SQL conditions that were present in DA when the + handler was activated. + */ + da->remove_marked_sql_conditions(); + + uint continue_ip= f->continue_ip; + + DBUG_RETURN(continue_ip); +} + + +int sp_rcontext::set_variable(THD *thd, uint idx, Item **value) +{ + DBUG_ENTER("sp_rcontext::set_variable"); + DBUG_ASSERT(value); + DBUG_RETURN(thd->sp_eval_expr(m_var_table->field[idx], value)); +} + + +int sp_rcontext::set_variable_row_field(THD *thd, uint var_idx, uint field_idx, + Item **value) +{ + DBUG_ENTER("sp_rcontext::set_variable_row_field"); + DBUG_ASSERT(value); + Virtual_tmp_table *vtable= virtual_tmp_table_for_row(var_idx); + DBUG_RETURN(thd->sp_eval_expr(vtable->field[field_idx], value)); +} + + +int sp_rcontext::set_variable_row_field_by_name(THD *thd, uint var_idx, + const LEX_CSTRING &field_name, + Item **value) +{ + DBUG_ENTER("sp_rcontext::set_variable_row_field_by_name"); + uint field_idx; + if (find_row_field_by_name_or_error(&field_idx, var_idx, field_name)) + DBUG_RETURN(1); + DBUG_RETURN(set_variable_row_field(thd, var_idx, field_idx, value)); +} + + +int sp_rcontext::set_variable_row(THD *thd, uint var_idx, List<Item> &items) +{ + DBUG_ENTER("sp_rcontext::set_variable_row"); + DBUG_ASSERT(get_variable(var_idx)->cols() == items.elements); + Virtual_tmp_table *vtable= virtual_tmp_table_for_row(var_idx); + Sp_eval_expr_state state(thd); + DBUG_RETURN(vtable->sp_set_all_fields_from_item_list(thd, items)); +} + + +Virtual_tmp_table *sp_rcontext::virtual_tmp_table_for_row(uint var_idx) +{ + DBUG_ASSERT(get_variable(var_idx)->type() == Item::FIELD_ITEM); + DBUG_ASSERT(get_variable(var_idx)->cmp_type() == ROW_RESULT); + Field *field= m_var_table->field[var_idx]; + Virtual_tmp_table **ptable= field->virtual_tmp_table_addr(); + DBUG_ASSERT(ptable); + DBUG_ASSERT(ptable[0]); + return ptable[0]; +} + + +bool sp_rcontext::find_row_field_by_name_or_error(uint *field_idx, + uint var_idx, + const LEX_CSTRING &field_name) +{ + Virtual_tmp_table *vtable= virtual_tmp_table_for_row(var_idx); + Field *row= m_var_table->field[var_idx]; + return vtable->sp_find_field_by_name_or_error(field_idx, + row->field_name, field_name); +} + + +Item_cache *sp_rcontext::create_case_expr_holder(THD *thd, + const Item *item) const +{ + Item_cache *holder; + Query_arena current_arena; + + thd->set_n_backup_active_arena(thd->spcont->callers_arena, ¤t_arena); + + holder= item->get_cache(thd); + + thd->restore_active_arena(thd->spcont->callers_arena, ¤t_arena); + + return holder; +} + + +bool sp_rcontext::set_case_expr(THD *thd, int case_expr_id, + Item **case_expr_item_ptr) +{ + Item *case_expr_item= thd->sp_prepare_func_item(case_expr_item_ptr, 1); + if (!case_expr_item) + return true; + + if (!m_case_expr_holders[case_expr_id] || + m_case_expr_holders[case_expr_id]->result_type() != + case_expr_item->result_type()) + { + m_case_expr_holders[case_expr_id]= + create_case_expr_holder(thd, case_expr_item); + } + + m_case_expr_holders[case_expr_id]->store(case_expr_item); + m_case_expr_holders[case_expr_id]->cache_value(); + return false; +} + + +/////////////////////////////////////////////////////////////////////////// +// sp_cursor implementation. +/////////////////////////////////////////////////////////////////////////// + + +/* + Open an SP cursor + + SYNOPSIS + open() + THD Thread handler + + + RETURN + 0 in case of success, -1 otherwise +*/ + +int sp_cursor::open(THD *thd) +{ + if (server_side_cursor) + { + my_message(ER_SP_CURSOR_ALREADY_OPEN, + ER_THD(thd, ER_SP_CURSOR_ALREADY_OPEN), + MYF(0)); + return -1; + } + if (mysql_open_cursor(thd, &result, &server_side_cursor)) + return -1; + return 0; +} + + +int sp_cursor::close(THD *thd) +{ + if (! server_side_cursor) + { + my_message(ER_SP_CURSOR_NOT_OPEN, ER_THD(thd, ER_SP_CURSOR_NOT_OPEN), + MYF(0)); + return -1; + } + sp_cursor_statistics::reset(); + destroy(); + return 0; +} + + +void sp_cursor::destroy() +{ + delete server_side_cursor; + server_side_cursor= NULL; +} + + +int sp_cursor::fetch(THD *thd, List<sp_variable> *vars, bool error_on_no_data) +{ + if (! server_side_cursor) + { + my_message(ER_SP_CURSOR_NOT_OPEN, ER_THD(thd, ER_SP_CURSOR_NOT_OPEN), + MYF(0)); + return -1; + } + if (vars->elements != result.get_field_count() && + (vars->elements != 1 || + result.get_field_count() != + thd->spcont->get_variable(vars->head()->offset)->cols())) + { + my_message(ER_SP_WRONG_NO_OF_FETCH_ARGS, + ER_THD(thd, ER_SP_WRONG_NO_OF_FETCH_ARGS), MYF(0)); + return -1; + } + + m_fetch_count++; + DBUG_EXECUTE_IF("bug23032_emit_warning", + push_warning(thd, Sql_condition::WARN_LEVEL_WARN, + ER_UNKNOWN_ERROR, + ER_THD(thd, ER_UNKNOWN_ERROR));); + + result.set_spvar_list(vars); + + DBUG_ASSERT(!thd->is_error()); + + /* Attempt to fetch one row */ + if (server_side_cursor->is_open()) + { + server_side_cursor->fetch(1); + if (thd->is_error()) + return -1; // e.g. data type conversion failed + } + + /* + If the cursor was pointing after the last row, the fetch will + close it instead of sending any rows. + */ + if (! server_side_cursor->is_open()) + { + m_found= false; + if (!error_on_no_data) + return 0; + my_message(ER_SP_FETCH_NO_DATA, ER_THD(thd, ER_SP_FETCH_NO_DATA), MYF(0)); + return -1; + } + + m_found= true; + m_row_count++; + return 0; +} + + +bool sp_cursor::export_structure(THD *thd, Row_definition_list *list) +{ + return server_side_cursor->export_structure(thd, list); +} + +/////////////////////////////////////////////////////////////////////////// +// sp_cursor::Select_fetch_into_spvars implementation. +/////////////////////////////////////////////////////////////////////////// + + +int sp_cursor::Select_fetch_into_spvars::prepare(List<Item> &fields, + SELECT_LEX_UNIT *u) +{ + /* + Cache the number of columns in the result set in order to easily + return an error if column count does not match value count. + */ + field_count= fields.elements; + return select_result_interceptor::prepare(fields, u); +} + + +bool sp_cursor::Select_fetch_into_spvars:: + send_data_to_variable_list(List<sp_variable> &vars, List<Item> &items) +{ + List_iterator_fast<sp_variable> spvar_iter(vars); + List_iterator_fast<Item> item_iter(items); + sp_variable *spvar; + Item *item; + + /* Must be ensured by the caller */ + DBUG_ASSERT(vars.elements == items.elements); + + /* + Assign the row fetched from a server side cursor to stored + procedure variables. + */ + for (; spvar= spvar_iter++, item= item_iter++; ) + { + if (thd->spcont->set_variable(thd, spvar->offset, &item)) + return true; + } + return false; +} + + +int sp_cursor::Select_fetch_into_spvars::send_data(List<Item> &items) +{ + Item *item; + /* + If we have only one variable in spvar_list, and this is a ROW variable, + and the number of fields in the ROW variable matches the number of + fields in the query result, we fetch to this ROW variable. + + If there is one variable, and it is a ROW variable, but its number + of fields does not match the number of fields in the query result, + we go through send_data_to_variable_list(). It will report an error + on attempt to assign a scalar value to a ROW variable. + */ + return spvar_list->elements == 1 && + (item= thd->spcont->get_variable(spvar_list->head()->offset)) && + item->type_handler() == &type_handler_row && + item->cols() == items.elements ? + thd->spcont->set_variable_row(thd, spvar_list->head()->offset, items) : + send_data_to_variable_list(*spvar_list, items); +} |