mariadb/sql/sp_pcontext.cc
unknown 6b2f13098a Patch for WL#2894: Make stored routine variables work
according to the standard.

The idea is to use Field-classes to implement stored routines
variables. Also, we should provide facade to Item-hierarchy
by Item_field class (it is necessary, since SRVs take part
in expressions).

The patch fixes the following bugs:
  - BUG#8702: Stored Procedures: No Error/Warning shown for inappropriate data 
    type matching; 
 
  - BUG#8768: Functions: For any unsigned data type, -ve values can be passed 
    and returned; 
 
  - BUG#8769: Functions: For Int datatypes, out of range values can be passed 
    and returned; 
 
  - BUG#9078: STORED PROCDURE: Decimal digits are not displayed when we use 
    DECIMAL datatype; 
 
  - BUG#9572: Stored procedures: variable type declarations ignored; 
 
  - BUG#12903: upper function does not work inside a function; 
 
  - BUG#13705: parameters to stored procedures are not verified; 
 
  - BUG#13808: ENUM type stored procedure parameter accepts non-enumerated
    data; 
 
  - BUG#13909: Varchar Stored Procedure Parameter always BINARY string (ignores 
    CHARACTER SET); 
 
  - BUG#14161: Stored procedure cannot retrieve bigint unsigned;

  - BUG#14188: BINARY variables have no 0x00 padding;

  - BUG#15148: Stored procedure variables accept non-scalar values;


mysql-test/r/ctype_ujis.result:
  Explicitly specify correct charset.
mysql-test/r/schema.result:
  Drop our test database to not affect this test if some test
  left it cause of failure.
mysql-test/r/show_check.result:
  Drop our test database to not affect this test if some test
  left it cause of failure.
mysql-test/r/skip_name_resolve.result:
  Ignore columns with unpredictable values.
mysql-test/r/sp-big.result:
  Add cleanup statement.
mysql-test/r/sp-dynamic.result:
  Add cleanup statements.
mysql-test/r/sp.result:
  Update result file.
mysql-test/r/sum_distinct-big.result:
  Update result file.
mysql-test/r/type_newdecimal-big.result:
  Update result file.
mysql-test/t/ctype_ujis.test:
  Explicitly specify correct charset.
mysql-test/t/schema.test:
  Drop our test database to not affect this test if some test
  left it cause of failure.
mysql-test/t/show_check.test:
  Drop our test database to not affect this test if some test
  left it cause of failure.
mysql-test/t/skip_name_resolve.test:
  Ignore columns with unpredictable values.
mysql-test/t/sp-big.test:
  Add cleanup statement.
mysql-test/t/sp-dynamic.test:
  Add cleanup statements.
mysql-test/t/sp.test:
  Non-scalar values prohibited for assignment to SP-vars;
  polishing.
mysql-test/t/type_newdecimal-big.test:
  Update type specification so that the variables
  can contain the large values used in the test.
sql/field.cc:
  Extract create_field::init() to initialize an existing
  instance of create_field from new_create_field().
sql/field.h:
  Extract create_field::init() to initialize an existing
  instance of create_field from new_create_field().
sql/item.cc:
  - Introduce a new class: Item_sp_variable -- a base class
    of stored-routine-variables classes;
  - Introduce Item_case_expr -- an Item, which is used to access
    to the expression of CASE statement;
sql/item.h:
  - Introduce a new class: Item_sp_variable -- a base class
    of stored-routine-variables classes;
  - Introduce Item_case_expr -- an Item, which is used to access
    to the expression of CASE statement;
sql/item_func.cc:
  Pass the Field (instead of Item) for the return value of
  a function to the function execution routine.
sql/item_func.h:
  Pass the Field (instead of Item) for the return value of
  a function to the function execution routine.
sql/mysql_priv.h:
  Move create_virtual_tmp_table() out of sql_select.h.
sql/sp.cc:
  Use create_result_field() instead of make_field().
sql/sp_head.cc:
  - Add a function to map enum_field_types to Item::Type;
  - Add sp_instr_push_case_expr instruction -- an instruction
    to push CASE expression into the active running context;
  - Add sp_instr_pop_case_expr instruction -- an instruction
    to pop CASE expression from the active running context;
  - Adapt the SP-execution code to using Fields instead of Items
    for SP-vars;
  - Use create_field structure for field description instead of
    a set of members.
sql/sp_head.h:
  - Add a function to map enum_field_types to Item::Type;
  - Add sp_instr_push_case_expr instruction -- an instruction
    to push CASE expression into the active running context;
  - Add sp_instr_pop_case_expr instruction -- an instruction
    to pop CASE expression from the active running context;
  - Adapt the SP-execution code to using Fields instead of Items
    for SP-vars;
  - Use create_field structure for field description instead of
    a set of members.
sql/sp_pcontext.cc:
  - Change rules to assign an index of SP-variable: use
    transparent index;
  - Add an operation to retrieve a list of defined SP-vars
    from the processing context recursively.
sql/sp_pcontext.h:
  - Change rules to assign an index of SP-variable: use
    transparent index;
  - Add an operation to retrieve a list of defined SP-vars
    from the processing context recursively.
sql/sp_rcontext.cc:
  - Change rules to assign an index of SP-variable: use
    transparent index;
  - Use a tmp virtual table to store SP-vars instead of Items;
  - Provide operations to work with CASE expresion.
sql/sp_rcontext.h:
  - Change rules to assign an index of SP-variable: use
    transparent index;
  - Use a tmp virtual table to store SP-vars instead of Items;
  - Provide operations to work with CASE expresion.
sql/sql_class.cc:
  - Reflect Item_splocal ctor changes;
  - Item_splocal::get_offset() has been renamed to get_var_idx().
sql/sql_class.h:
  Polishing.
sql/sql_parse.cc:
  Extract create_field::init() to initialize an existing
  instance of create_field from new_create_field().
sql/sql_select.cc:
  Take care of BLOB columns in create_virtual_tmp_table().
sql/sql_select.h:
  Move create_virtual_tmp_table() out of sql_select.h.
sql/sql_trigger.cc:
  Use boolean constants for boolean type instead of numerical ones.
sql/sql_yacc.yy:
  Provide an instance of create_field for each SP-var.
mysql-test/include/sp-vars.inc:
  The definitions of common-procedures, which are created
  under different circumstances.
mysql-test/r/sp-vars.result:
  Result file for the SP-vars test.
mysql-test/sp-vars.test:
  A new test for checking SP-vars functionality.
2005-12-07 17:01:17 +03:00

408 lines
9.1 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 "sp_pcontext.h"
#include "sp_head.h"
/*
* Sanity check for SQLSTATEs. Will not check if it's really an existing
* state (there are just too many), but will check length and bad characters.
* Returns TRUE if it's ok, FALSE if it's bad.
*/
bool
sp_cond_check(LEX_STRING *sqlstate)
{
int i;
const char *p;
if (sqlstate->length != 5)
return FALSE;
for (p= sqlstate->str, i= 0 ; i < 5 ; i++)
{
char c = p[i];
if ((c < '0' || '9' < c) &&
(c < 'A' || 'Z' < c))
return FALSE;
}
return TRUE;
}
sp_pcontext::sp_pcontext(sp_pcontext *prev)
:Sql_alloc(), m_total_pvars(0), m_csubsize(0), m_hsubsize(0),
m_handlers(0), m_parent(prev), m_pboundary(0)
{
VOID(my_init_dynamic_array(&m_pvar, sizeof(sp_pvar_t *), 16, 8));
VOID(my_init_dynamic_array(&m_case_expr_id_lst, sizeof(int), 16, 8));
VOID(my_init_dynamic_array(&m_cond, sizeof(sp_cond_type_t *), 16, 8));
VOID(my_init_dynamic_array(&m_cursor, sizeof(LEX_STRING), 16, 8));
VOID(my_init_dynamic_array(&m_handler, sizeof(sp_cond_type_t *), 16, 8));
m_label.empty();
m_children.empty();
if (!prev)
{
m_poffset= m_coffset= 0;
m_num_case_exprs= 0;
}
else
{
m_poffset= prev->m_poffset + prev->m_total_pvars;
m_coffset= prev->current_cursors();
m_num_case_exprs= prev->get_num_case_exprs();
}
}
void
sp_pcontext::destroy()
{
List_iterator_fast<sp_pcontext> li(m_children);
sp_pcontext *child;
while ((child= li++))
child->destroy();
m_children.empty();
m_label.empty();
delete_dynamic(&m_pvar);
delete_dynamic(&m_case_expr_id_lst);
delete_dynamic(&m_cond);
delete_dynamic(&m_cursor);
delete_dynamic(&m_handler);
}
sp_pcontext *
sp_pcontext::push_context()
{
sp_pcontext *child= new sp_pcontext(this);
if (child)
m_children.push_back(child);
return child;
}
sp_pcontext *
sp_pcontext::pop_context()
{
m_parent->m_total_pvars= m_parent->m_total_pvars + m_total_pvars;
uint submax= max_handlers();
if (submax > m_parent->m_hsubsize)
m_parent->m_hsubsize= submax;
submax= max_cursors();
if (submax > m_parent->m_csubsize)
m_parent->m_csubsize= submax;
if (m_num_case_exprs > m_parent->m_num_case_exprs)
m_parent->m_num_case_exprs= m_num_case_exprs;
return m_parent;
}
uint
sp_pcontext::diff_handlers(sp_pcontext *ctx)
{
uint n= 0;
sp_pcontext *pctx= this;
while (pctx && pctx != ctx)
{
n+= pctx->m_handlers;
pctx= pctx->parent_context();
}
if (pctx)
return n;
return 0; // Didn't find ctx
}
uint
sp_pcontext::diff_cursors(sp_pcontext *ctx)
{
sp_pcontext *pctx= this;
while (pctx && pctx != ctx)
pctx= pctx->parent_context();
if (pctx)
return ctx->current_cursors() - pctx->current_cursors();
return 0; // Didn't find ctx
}
/* This does a linear search (from newer to older variables, in case
** we have shadowed names).
** It's possible to have a more efficient allocation and search method,
** but it might not be worth it. The typical number of parameters and
** variables will in most cases be low (a handfull).
** ...and, this is only called during parsing.
*/
sp_pvar_t *
sp_pcontext::find_pvar(LEX_STRING *name, my_bool scoped)
{
uint i= m_pvar.elements - m_pboundary;
while (i--)
{
sp_pvar_t *p;
get_dynamic(&m_pvar, (gptr)&p, i);
if (my_strnncoll(system_charset_info,
(const uchar *)name->str, name->length,
(const uchar *)p->name.str, p->name.length) == 0)
{
return p;
}
}
if (!scoped && m_parent)
return m_parent->find_pvar(name, scoped);
return NULL;
}
/*
Find a variable by offset from the top.
This used for two things:
- When evaluating parameters at the beginning, and setting out parameters
at the end, of invokation. (Top frame only, so no recursion then.)
- For printing of sp_instr_set. (Debug mode only.)
*/
sp_pvar_t *
sp_pcontext::find_pvar(uint offset)
{
if (m_poffset <= offset && offset < m_poffset + m_pvar.elements)
{ // This frame
sp_pvar_t *p;
get_dynamic(&m_pvar, (gptr)&p, offset - m_poffset);
return p;
}
if (m_parent)
return m_parent->find_pvar(offset); // Some previous frame
return NULL; // index out of bounds
}
sp_pvar_t *
sp_pcontext::push_pvar(LEX_STRING *name, enum enum_field_types type,
sp_param_mode_t mode)
{
sp_pvar_t *p= (sp_pvar_t *)sql_alloc(sizeof(sp_pvar_t));
if (!p)
return NULL;
++m_total_pvars;
p->name.str= name->str;
p->name.length= name->length;
p->type= type;
p->mode= mode;
p->offset= current_pvars();
p->dflt= NULL;
insert_dynamic(&m_pvar, (gptr)&p);
return p;
}
sp_label_t *
sp_pcontext::push_label(char *name, uint ip)
{
sp_label_t *lab = (sp_label_t *)sql_alloc(sizeof(sp_label_t));
if (lab)
{
lab->name= name;
lab->ip= ip;
lab->type= SP_LAB_GOTO;
lab->ctx= this;
m_label.push_front(lab);
}
return lab;
}
sp_label_t *
sp_pcontext::find_label(char *name)
{
List_iterator_fast<sp_label_t> li(m_label);
sp_label_t *lab;
while ((lab= li++))
if (my_strcasecmp(system_charset_info, name, lab->name) == 0)
return lab;
if (m_parent)
return m_parent->find_label(name);
return NULL;
}
void
sp_pcontext::push_cond(LEX_STRING *name, sp_cond_type_t *val)
{
sp_cond_t *p= (sp_cond_t *)sql_alloc(sizeof(sp_cond_t));
if (p)
{
p->name.str= name->str;
p->name.length= name->length;
p->val= val;
insert_dynamic(&m_cond, (gptr)&p);
}
}
/*
* See comment for find_pvar() above
*/
sp_cond_type_t *
sp_pcontext::find_cond(LEX_STRING *name, my_bool scoped)
{
uint i= m_cond.elements;
while (i--)
{
sp_cond_t *p;
get_dynamic(&m_cond, (gptr)&p, i);
if (my_strnncoll(system_charset_info,
(const uchar *)name->str, name->length,
(const uchar *)p->name.str, p->name.length) == 0)
{
return p->val;
}
}
if (!scoped && m_parent)
return m_parent->find_cond(name, scoped);
return NULL;
}
/*
* This only searches the current context, for error checking of
* duplicates.
* Returns TRUE if found.
*/
bool
sp_pcontext::find_handler(sp_cond_type_t *cond)
{
uint i= m_handler.elements;
while (i--)
{
sp_cond_type_t *p;
get_dynamic(&m_handler, (gptr)&p, i);
if (cond->type == p->type)
{
switch (p->type)
{
case sp_cond_type_t::number:
if (cond->mysqlerr == p->mysqlerr)
return TRUE;
break;
case sp_cond_type_t::state:
if (strcmp(cond->sqlstate, p->sqlstate) == 0)
return TRUE;
break;
default:
return TRUE;
}
}
}
return FALSE;
}
void
sp_pcontext::push_cursor(LEX_STRING *name)
{
LEX_STRING n;
if (m_cursor.elements == m_csubsize)
m_csubsize+= 1;
n.str= name->str;
n.length= name->length;
insert_dynamic(&m_cursor, (gptr)&n);
}
/*
* See comment for find_pvar() above
*/
my_bool
sp_pcontext::find_cursor(LEX_STRING *name, uint *poff, my_bool scoped)
{
uint i= m_cursor.elements;
while (i--)
{
LEX_STRING n;
get_dynamic(&m_cursor, (gptr)&n, i);
if (my_strnncoll(system_charset_info,
(const uchar *)name->str, name->length,
(const uchar *)n.str, n.length) == 0)
{
*poff= m_coffset + i;
return TRUE;
}
}
if (!scoped && m_parent)
return m_parent->find_cursor(name, poff, scoped);
return FALSE;
}
void
sp_pcontext::retrieve_field_definitions(List<create_field> *field_def_lst)
{
/* Put local/context fields in the result list. */
for (uint i = 0; i < m_pvar.elements; ++i)
{
sp_pvar_t *var_def;
get_dynamic(&m_pvar, (gptr) &var_def, i);
field_def_lst->push_back(&var_def->field_def);
}
/* Put the fields of the enclosed contexts in the result list. */
List_iterator_fast<sp_pcontext> li(m_children);
sp_pcontext *ctx;
while ((ctx = li++))
ctx->retrieve_field_definitions(field_def_lst);
}
/*
Find a cursor by offset from the top.
This is only used for debugging.
*/
my_bool
sp_pcontext::find_cursor(uint offset, LEX_STRING *n)
{
if (m_coffset <= offset && offset < m_coffset + m_cursor.elements)
{ // This frame
get_dynamic(&m_cursor, (gptr)n, offset - m_coffset);
return TRUE;
}
if (m_parent)
return m_parent->find_cursor(offset, n); // Some previous frame
return FALSE; // index out of bounds
}