mirror of
				https://github.com/MariaDB/server.git
				synced 2025-10-31 19:06:14 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			908 lines
		
	
	
	
		
			26 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			908 lines
		
	
	
	
		
			26 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /* 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());
 | |
|     }
 | |
|   }
 | |
|   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);
 | |
|   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);
 | |
| }
 | 
