mirror of
				https://github.com/MariaDB/server.git
				synced 2025-11-04 04:46:15 +01:00 
			
		
		
		
	This patch adds support for SYS_REFCURSOR (a weakly typed cursor)
for both sql_mode=ORACLE and sql_mode=DEFAULT.
Works as a regular stored routine variable, parameter and return value:
- can be passed as an IN parameter to stored functions and procedures
- can be passed as an INOUT and OUT parameter to stored procedures
- can be returned from a stored function
Note, strongly typed REF CURSOR will be added separately.
Note, to maintain dependencies easier, some parts of sql_class.h
and item.h were moved to new header files:
- select_results.h:
  class select_result_sink
  class select_result
  class select_result_interceptor
- sp_cursor.h:
  class sp_cursor_statistics
  class sp_cursor
- sp_rcontext_handler.h
  class Sp_rcontext_handler and its descendants
The implementation consists of the following parts:
- A new class sp_cursor_array deriving from Dynamic_array
- A new class Statement_rcontext which contains data shared
  between sub-statements of a compound statement.
  It has a member m_statement_cursors of the sp_cursor_array data type,
  as well as open cursor counter. THD inherits from Statement_rcontext.
- A new data type handler Type_handler_sys_refcursor in plugins/type_cursor/
  It is designed to store uint16 references -
  positions of the cursor in THD::m_statement_cursors.
- Type_handler_sys_refcursor suppresses some derived numeric features.
  When a SYS_REFCURSOR variable is used as an integer an error is raised.
- A new abstract class sp_instr_fetch_cursor. It's needed to share
  the common code between "OPEN cur" (for static cursors) and
  "OPER cur FOR stmt" (for SYS_REFCURSORs).
- New sp_instr classes:
  * sp_instr_copen_by_ref      - OPEN sys_ref_curor FOR stmt;
  * sp_instr_cfetch_by_ref     - FETCH sys_ref_cursor INTO targets;
  * sp_instr_cclose_by_ref     - CLOSE sys_ref_cursor;
  * sp_instr_destruct_variable - to destruct SYS_REFCURSOR variables when
                                 the execution goes out of the BEGIN..END block
                                 where SYS_REFCURSOR variables are declared.
- New methods in LEX:
  * sp_open_cursor_for_stmt   - handles "OPEN sys_ref_cursor FOR stmt".
  * sp_add_instr_fetch_cursor - "FETCH cur INTO targets" for both
                                static cursors and SYS_REFCURSORs.
  * sp_close - handles "CLOSE cur" both for static cursors and SYS_REFCURSORs.
- Changes in cursor functions to handle both static cursors and SYS_REFCURSORs:
  * Item_func_cursor_isopen
  * Item_func_cursor_found
  * Item_func_cursor_notfound
  * Item_func_cursor_rowcount
- A new system variable @@max_open_cursors - to limit the number
  of cursors (static and SYS_REFCURSORs) opened at the same time.
  Its allowed range is [0-65536], with 50 by default.
- A new virtual method Type_handler::can_return_bool() telling
  if calling item->val_bool() is allowed for Items of this data type,
  or if otherwise the "Illegal parameter for operation" error should be raised
  at fix_fields() time.
- New methods in Sp_rcontext_handler:
  * get_cursor()
  * get_cursor_by_ref()
- A new class Sp_rcontext_handler_statement to handle top level statement
  wide cursors which are shared by all substatements.
- A new virtual method expr_event_handler() in classes Item and Field.
  It's needed to close (and make available for a new OPEN)
  unused THD::m_statement_cursors elements which do not have any references
  any more. It can happen in various moments in time, e.g.
  * after evaluation parameters of an SQL routine
  * after assigning a cursor expression into a SYS_REFCURSOR variable
  * when leaving a BEGIN..END block with SYS_REFCURSOR variables
  * after setting OUT/INOUT routine actual parameters from formal
    parameters.
		
	
			
		
			
				
	
	
		
			949 lines
		
	
	
	
		
			28 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			949 lines
		
	
	
	
		
			28 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"
 | 
						|
#include "mysql.h"
 | 
						|
#include "sp_head.h"
 | 
						|
#include "sql_cursor.h"
 | 
						|
#include "sp_instr.h"                       // class sp_instr, ...
 | 
						|
#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_handler_statement sp_rcontext_handler_statement;
 | 
						|
 | 
						|
 | 
						|
sp_cursor *Sp_rcontext_handler::get_open_cursor_or_error(THD *thd,
 | 
						|
                                                 const sp_rcontext_ref &ref)
 | 
						|
{
 | 
						|
  sp_cursor *cursor= get_cursor(thd, ref);
 | 
						|
  if (cursor && cursor->is_open())
 | 
						|
    return cursor;
 | 
						|
  my_error(ER_SP_CURSOR_NOT_OPEN, MYF(0));
 | 
						|
  return nullptr;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
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;
 | 
						|
}
 | 
						|
 | 
						|
const LEX_CSTRING *Sp_rcontext_handler_statement::get_name_prefix() const
 | 
						|
{
 | 
						|
  static const LEX_CSTRING prefix= {STRING_WITH_LEN("STMT.")};
 | 
						|
  return &prefix;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
Item_field *Sp_rcontext_handler_local::get_variable(THD *thd,
 | 
						|
                                                    uint offset) const
 | 
						|
{
 | 
						|
  return thd->spcont->get_variable(offset);
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
Item_field *Sp_rcontext_handler_package_body::get_variable(THD *thd,
 | 
						|
                                                           uint offset) const
 | 
						|
{
 | 
						|
  return Sp_rcontext_handler_package_body::get_rcontext(thd->spcont)->
 | 
						|
                                             get_variable(offset);
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
sp_cursor *Sp_rcontext_handler_local::get_cursor(THD *thd, uint offset) const
 | 
						|
{
 | 
						|
  return thd->spcont->get_cursor(offset);
 | 
						|
}
 | 
						|
 | 
						|
sp_cursor *Sp_rcontext_handler_statement::get_cursor(THD *thd, uint offset) const
 | 
						|
{
 | 
						|
  return &thd->statement_cursors()->at(offset);
 | 
						|
}
 | 
						|
 | 
						|
sp_cursor *Sp_rcontext_handler_statement::get_cursor_by_ref(THD *thd,
 | 
						|
                                            const sp_rcontext_addr &ref,
 | 
						|
                                            bool for_open) const
 | 
						|
{
 | 
						|
  Field *field= ref.rcontext_handler()->get_variable(thd, ref.offset())->field;
 | 
						|
  return thd->statement_cursors()->get_cursor_by_ref(thd, field, for_open);
 | 
						|
}
 | 
						|
 | 
						|
///////////////////////////////////////////////////////////////////////////
 | 
						|
// sp_rcontext implementation.
 | 
						|
///////////////////////////////////////////////////////////////////////////
 | 
						|
 | 
						|
 | 
						|
sp_rcontext::sp_rcontext(sp_head *owner,
 | 
						|
                         const sp_pcontext *root_parsing_ctx,
 | 
						|
                         Field *return_value_fld,
 | 
						|
                         bool in_sub_stmt)
 | 
						|
  :callers_arena(nullptr), 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),
 | 
						|
   m_inited_params_count(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,
 | 
						|
                                 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(thd->alloc<sp_cursor*>(n), n);
 | 
						|
  }
 | 
						|
 | 
						|
  {
 | 
						|
    size_t n= m_root_parsing_ctx->get_num_case_exprs();
 | 
						|
    m_case_expr_holders.reset(thd->calloc<Item_cache *>(n), 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 Lex_ident_column &name,
 | 
						|
                                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, name, 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) const
 | 
						|
{
 | 
						|
  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, (Table_ident*)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, 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.
 | 
						|
      */
 | 
						|
      const Lex_ident_column tmp= src[0]->field_name;
 | 
						|
      Spvar_definition *def;
 | 
						|
      if ((rc= check_column_grant_for_type_ref(thd, table_list, tmp, 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_row())
 | 
						|
    {
 | 
						|
      if (def->row_field_definitions()->resolve_type_refs(thd))
 | 
						|
        return true;
 | 
						|
    }
 | 
						|
    else if (def->is_column_type_ref() &&
 | 
						|
        def->column_type_ref()->resolve_type_ref(thd, def))
 | 
						|
      return true;
 | 
						|
  }
 | 
						|
  return false;
 | 
						|
};
 | 
						|
 | 
						|
 | 
						|
Item_field_row *Spvar_definition::make_item_field_row(THD *thd,
 | 
						|
                                                      Field_row *field)
 | 
						|
{
 | 
						|
  Item_field_row *item= new (thd->mem_root) Item_field_row(thd, field);
 | 
						|
  if (!item)
 | 
						|
    return nullptr;
 | 
						|
 | 
						|
  if (field->row_create_fields(thd, *this))
 | 
						|
    return nullptr;
 | 
						|
 | 
						|
  // field->virtual_tmp_table() returns nullptr in case of ROW TYPE OF cursor
 | 
						|
  if (field->virtual_tmp_table() &&
 | 
						|
      item->add_array_of_item_field(thd, *field->virtual_tmp_table()))
 | 
						|
    return nullptr;
 | 
						|
 | 
						|
  return item;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
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(thd->alloc<Item_field*>(num_vars), 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];
 | 
						|
    Field_row *field_row= dynamic_cast<Field_row*>(field);
 | 
						|
    if (!(m_var_items[idx]= field_row ?
 | 
						|
                            def->make_item_field_row(thd, field_row) :
 | 
						|
                            new (thd->mem_root) Item_field(thd, field)))
 | 
						|
      return true;
 | 
						|
  }
 | 
						|
  return false;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
void sp_rcontext::expr_event_handler(THD *thd, expr_event_t event,
 | 
						|
                                     uint start, uint end)
 | 
						|
{
 | 
						|
  if (m_var_table)
 | 
						|
    m_var_table->expr_event_handler(thd, event, start, end);
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
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];
 | 
						|
  DBUG_ASSERT(field->virtual_tmp_table());
 | 
						|
  return field->virtual_tmp_table();
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
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())
 | 
						|
  {
 | 
						|
    if (!(m_case_expr_holders[case_expr_id]=
 | 
						|
          create_case_expr_holder(thd, case_expr_item)))
 | 
						|
      return true; // A data type not allowed in CASE WHEN, or EOM
 | 
						|
  }
 | 
						|
 | 
						|
  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, bool check_open_cursor_counter)
 | 
						|
{
 | 
						|
  if (server_side_cursor)
 | 
						|
  {
 | 
						|
    my_message(ER_SP_CURSOR_ALREADY_OPEN,
 | 
						|
               ER_THD(thd, ER_SP_CURSOR_ALREADY_OPEN),
 | 
						|
               MYF(0));
 | 
						|
    return -1;
 | 
						|
  }
 | 
						|
 | 
						|
  if (check_open_cursor_counter &&
 | 
						|
      thd->open_cursors_counter() >= thd->variables.max_open_cursors)
 | 
						|
  {
 | 
						|
    my_error(ER_TOO_MANY_OPEN_CURSORS, MYF(0),
 | 
						|
             thd->variables.max_open_cursors);
 | 
						|
    return -1;
 | 
						|
  }
 | 
						|
 | 
						|
  if (mysql_open_cursor(thd, &result, &server_side_cursor))
 | 
						|
    return -1;
 | 
						|
  thd->open_cursors_counter_increment();
 | 
						|
  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;
 | 
						|
  }
 | 
						|
  thd->open_cursors_counter_decrement();
 | 
						|
  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_fetch_target> *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->get_variable(*vars->head())->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_fetch_target> &vars,
 | 
						|
                                  List<Item> &items)
 | 
						|
{
 | 
						|
  List_iterator_fast<sp_fetch_target> spvar_iter(vars);
 | 
						|
  List_iterator_fast<Item> item_iter(items);
 | 
						|
  sp_fetch_target *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->get_rcontext(*spvar)->set_variable(thd, spvar->offset(), &item))
 | 
						|
      return true;
 | 
						|
  }
 | 
						|
  return false;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
int sp_cursor::Select_fetch_into_spvars::send_data(List<Item> &items)
 | 
						|
{
 | 
						|
  /*
 | 
						|
    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.
 | 
						|
  */
 | 
						|
  if (m_fetch_target_list->elements == 1)
 | 
						|
  {
 | 
						|
    const sp_fetch_target *target= m_fetch_target_list->head();
 | 
						|
    sp_rcontext *rctx= thd->get_rcontext(*target);
 | 
						|
    Item *item;
 | 
						|
    if ((item= rctx->get_variable(target->offset())) &&
 | 
						|
        item->type_handler() == &type_handler_row &&
 | 
						|
        item->cols() == items.elements)
 | 
						|
    return rctx->set_variable_row(thd, target->offset(), items);
 | 
						|
  }
 | 
						|
  return send_data_to_variable_list(*m_fetch_target_list, items);
 | 
						|
}
 |