mirror of
https://github.com/MariaDB/server.git
synced 2025-01-16 20:12:31 +01:00
ad8ff14165
Replaced the dumb in-handler/not-in-handler check with a proper recursion check of handlers being executed. (Re-commit in a different tree, to make push possible.) mysql-test/r/sp.result: New test case for BUG#6127. mysql-test/t/sp.test: New test case for BUG#6127. sql/sp_head.cc: Replaced the setting of ctx->in_handler with a enter/exit handler methods. sql/sp_rcontext.cc: Replaced the boolean in_handler flag with a stack of handlers being exectuted, for proper recursion check. sql/sp_rcontext.h: Replaced the boolean in_handler flag with a stack of handlers being exectuted, for proper recursion check. (And added some comments in the sp_rcontext class.)
300 lines
6.7 KiB
C++
300 lines
6.7 KiB
C++
/* Copyright (C) 2002 MySQL AB
|
|
|
|
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; either version 2 of the License, or
|
|
(at your option) any later version.
|
|
|
|
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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
|
|
|
|
#include "mysql_priv.h"
|
|
#ifdef USE_PRAGMA_IMPLEMENTATION
|
|
#pragma implementation
|
|
#endif
|
|
|
|
#if defined(WIN32) || defined(__WIN__)
|
|
#undef SAFEMALLOC /* Problems with threads */
|
|
#endif
|
|
|
|
#include "mysql.h"
|
|
#include "sp_head.h"
|
|
#include "sql_cursor.h"
|
|
#include "sp_rcontext.h"
|
|
#include "sp_pcontext.h"
|
|
|
|
sp_rcontext::sp_rcontext(uint fsize, uint hmax, uint cmax)
|
|
: m_count(0), m_fsize(fsize), m_result(NULL), m_hcount(0), m_hsp(0),
|
|
m_ihsp(0), m_hfound(-1), m_ccount(0)
|
|
{
|
|
m_frame= (Item **)sql_alloc(fsize * sizeof(Item*));
|
|
m_handler= (sp_handler_t *)sql_alloc(hmax * sizeof(sp_handler_t));
|
|
m_hstack= (uint *)sql_alloc(hmax * sizeof(uint));
|
|
m_in_handler= (uint *)sql_alloc(hmax * sizeof(uint));
|
|
m_cstack= (sp_cursor **)sql_alloc(cmax * sizeof(sp_cursor *));
|
|
m_saved.empty();
|
|
}
|
|
|
|
|
|
int
|
|
sp_rcontext::set_item_eval(THD *thd, uint idx, Item **item_addr,
|
|
enum_field_types type)
|
|
{
|
|
Item *it;
|
|
Item *reuse_it;
|
|
/* sp_eval_func_item will use callers_arena */
|
|
int res;
|
|
|
|
reuse_it= get_item(idx);
|
|
it= sp_eval_func_item(thd, item_addr, type, reuse_it, TRUE);
|
|
if (! it)
|
|
res= -1;
|
|
else
|
|
{
|
|
res= 0;
|
|
set_item(idx, it);
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
bool
|
|
sp_rcontext::find_handler(uint sql_errno,
|
|
MYSQL_ERROR::enum_warning_level level)
|
|
{
|
|
if (m_hfound >= 0)
|
|
return 1; // Already got one
|
|
|
|
const char *sqlstate= mysql_errno_to_sqlstate(sql_errno);
|
|
int i= m_hcount, found= -1;
|
|
|
|
while (i--)
|
|
{
|
|
sp_cond_type_t *cond= m_handler[i].cond;
|
|
int j= m_ihsp;
|
|
|
|
while (j--)
|
|
if (m_in_handler[j] == m_handler[i].handler)
|
|
break;
|
|
if (j >= 0)
|
|
continue; // Already executing this handler
|
|
|
|
switch (cond->type)
|
|
{
|
|
case sp_cond_type_t::number:
|
|
if (sql_errno == cond->mysqlerr)
|
|
found= i; // Always the most specific
|
|
break;
|
|
case sp_cond_type_t::state:
|
|
if (strcmp(sqlstate, cond->sqlstate) == 0 &&
|
|
(found < 0 || m_handler[found].cond->type > sp_cond_type_t::state))
|
|
found= i;
|
|
break;
|
|
case sp_cond_type_t::warning:
|
|
if ((sqlstate[0] == '0' && sqlstate[1] == '1' ||
|
|
level == MYSQL_ERROR::WARN_LEVEL_WARN) &&
|
|
found < 0)
|
|
found= i;
|
|
break;
|
|
case sp_cond_type_t::notfound:
|
|
if (sqlstate[0] == '0' && sqlstate[1] == '2' &&
|
|
found < 0)
|
|
found= i;
|
|
break;
|
|
case sp_cond_type_t::exception:
|
|
if ((sqlstate[0] != '0' || sqlstate[1] > '2') &&
|
|
level == MYSQL_ERROR::WARN_LEVEL_ERROR &&
|
|
found < 0)
|
|
found= i;
|
|
break;
|
|
}
|
|
}
|
|
if (found < 0)
|
|
return FALSE;
|
|
m_hfound= found;
|
|
return TRUE;
|
|
}
|
|
|
|
void
|
|
sp_rcontext::save_variables(uint fp)
|
|
{
|
|
while (fp < m_count)
|
|
{
|
|
m_saved.push_front(m_frame[fp]);
|
|
m_frame[fp++]= NULL; // Prevent reuse
|
|
}
|
|
}
|
|
|
|
void
|
|
sp_rcontext::restore_variables(uint fp)
|
|
{
|
|
uint i= m_count;
|
|
|
|
while (i-- > fp)
|
|
m_frame[i]= m_saved.pop();
|
|
}
|
|
|
|
void
|
|
sp_rcontext::push_cursor(sp_lex_keeper *lex_keeper, sp_instr_cpush *i)
|
|
{
|
|
m_cstack[m_ccount++]= new sp_cursor(lex_keeper, i);
|
|
}
|
|
|
|
void
|
|
sp_rcontext::pop_cursors(uint count)
|
|
{
|
|
while (count--)
|
|
{
|
|
delete m_cstack[--m_ccount];
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
*
|
|
* sp_cursor
|
|
*
|
|
*/
|
|
|
|
sp_cursor::sp_cursor(sp_lex_keeper *lex_keeper, sp_instr_cpush *i)
|
|
:m_lex_keeper(lex_keeper),
|
|
server_side_cursor(NULL),
|
|
m_i(i)
|
|
{
|
|
/*
|
|
currsor can't be stored in QC, so we should prevent opening QC for
|
|
try to write results which are absent.
|
|
*/
|
|
lex_keeper->disable_query_cache();
|
|
}
|
|
|
|
|
|
/*
|
|
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(ER_SP_CURSOR_ALREADY_OPEN),
|
|
MYF(0));
|
|
return -1;
|
|
}
|
|
if (mysql_open_cursor(thd, (uint) ALWAYS_MATERIALIZED_CURSOR, &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(ER_SP_CURSOR_NOT_OPEN), MYF(0));
|
|
return -1;
|
|
}
|
|
destroy();
|
|
return 0;
|
|
}
|
|
|
|
|
|
void
|
|
sp_cursor::destroy()
|
|
{
|
|
delete server_side_cursor;
|
|
server_side_cursor= 0;
|
|
}
|
|
|
|
|
|
int
|
|
sp_cursor::fetch(THD *thd, List<struct sp_pvar> *vars)
|
|
{
|
|
if (! server_side_cursor)
|
|
{
|
|
my_message(ER_SP_CURSOR_NOT_OPEN, ER(ER_SP_CURSOR_NOT_OPEN), MYF(0));
|
|
return -1;
|
|
}
|
|
if (vars->elements != result.get_field_count())
|
|
{
|
|
my_message(ER_SP_WRONG_NO_OF_FETCH_ARGS,
|
|
ER(ER_SP_WRONG_NO_OF_FETCH_ARGS), MYF(0));
|
|
return -1;
|
|
}
|
|
|
|
result.set_spvar_list(vars);
|
|
|
|
/* Attempt to fetch one row */
|
|
if (server_side_cursor->is_open())
|
|
server_side_cursor->fetch(1);
|
|
|
|
/*
|
|
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())
|
|
{
|
|
my_message(ER_SP_FETCH_NO_DATA, ER(ER_SP_FETCH_NO_DATA), MYF(0));
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/***************************************************************************
|
|
Select_fetch_into_spvars
|
|
****************************************************************************/
|
|
|
|
int 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 Select_fetch_into_spvars::send_data(List<Item> &items)
|
|
{
|
|
List_iterator_fast<struct sp_pvar> pv_iter(*spvar_list);
|
|
List_iterator_fast<Item> item_iter(items);
|
|
sp_pvar_t *pv;
|
|
Item *item;
|
|
|
|
/* Must be ensured by the caller */
|
|
DBUG_ASSERT(spvar_list->elements == items.elements);
|
|
|
|
/*
|
|
Assign the row fetched from a server side cursor to stored
|
|
procedure variables.
|
|
*/
|
|
for (; pv= pv_iter++, item= item_iter++; )
|
|
{
|
|
Item *reuse= thd->spcont->get_item(pv->offset);
|
|
/* Evaluate a new item on the arena of the calling instruction */
|
|
Item *it= sp_eval_func_item(thd, &item, pv->type, reuse, TRUE);
|
|
|
|
thd->spcont->set_item(pv->offset, it);
|
|
}
|
|
return FALSE;
|
|
}
|