mirror of
https://github.com/MariaDB/server.git
synced 2025-01-16 03:52:35 +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);
|
|
}
|