mariadb/sql/sp_pcontext.cc
Iqbal Hassan dfdbec1636 MDEV-10862: Stored procedures: default values for parameters (optional parameters)
Implement default values for parameters of stored routines
in both default and oracle mode.

- Default values for cursor parameters are *NOT* supported yet.
- An IN parameter with DEFAULT followed by an OUT param is not supported yet.
  This combination will be enabled together with the arrow syntax:
    sp1(v=>p2)

The default values can be either literals or expressions.
When it is an expression, it is only evaluated if the parameter
has not been supplied by the caller
(important if the expression has side effects).
2025-01-31 19:42:04 +04:00

784 lines
20 KiB
C++

/* Copyright (c) 2002, 2010, Oracle and/or its affiliates. All rights reserved.
Copyright (c) 2009, 2020, MariaDB Corporation.
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 "sp_pcontext.h"
#include "sp_head.h"
bool sp_condition_value::equals(const sp_condition_value *cv) const
{
DBUG_ASSERT(cv);
/*
The following test disallows duplicate handlers,
including user defined exceptions with the same WHEN clause:
DECLARE
a EXCEPTION;
b EXCEPTION;
BEGIN
RAUSE a;
EXCEPTION
WHEN a THEN RETURN 'a0';
WHEN a THEN RETURN 'a1';
END
*/
if (this == cv)
return true;
/*
The test below considers two conditions of the same type as equal
(except for the user defined exceptions) to avoid declaring duplicate
handlers.
All user defined conditions have type==SQLSTATE
with the same SQL state and error code.
It's OK to have multiple user defined conditions:
DECLARE
a EXCEPTION;
b EXCEPTION;
BEGIN
RAISE a;
EXCEPTION
WHEN a THEN RETURN 'a';
WHEN b THEN RETURN 'b';
END;
*/
if (type != cv->type || m_is_user_defined || cv->m_is_user_defined)
return false;
switch (type)
{
case sp_condition_value::ERROR_CODE:
return (get_sql_errno() == cv->get_sql_errno());
case sp_condition_value::SQLSTATE:
return Sql_state::eq(cv);
default:
return true;
}
}
void sp_pcontext::init(uint var_offset,
uint cursor_offset,
int num_case_expressions)
{
m_var_offset= var_offset;
m_cursor_offset= cursor_offset;
m_num_case_exprs= num_case_expressions;
m_labels.empty();
m_goto_labels.empty();
}
sp_pcontext::sp_pcontext()
: Sql_alloc(),
m_max_var_index(0), m_max_cursor_index(0),
m_parent(NULL), m_pboundary(0),
m_vars(PSI_INSTRUMENT_MEM), m_case_expr_ids(PSI_INSTRUMENT_MEM),
m_conditions(PSI_INSTRUMENT_MEM), m_cursors(PSI_INSTRUMENT_MEM),
m_handlers(PSI_INSTRUMENT_MEM), m_records(PSI_INSTRUMENT_MEM),
m_children(PSI_INSTRUMENT_MEM), m_scope(REGULAR_SCOPE)
{
init(0, 0, 0);
}
sp_pcontext::sp_pcontext(sp_pcontext *prev, sp_pcontext::enum_scope scope)
: Sql_alloc(),
m_max_var_index(0), m_max_cursor_index(0),
m_parent(prev), m_pboundary(0),
m_vars(PSI_INSTRUMENT_MEM), m_case_expr_ids(PSI_INSTRUMENT_MEM),
m_conditions(PSI_INSTRUMENT_MEM), m_cursors(PSI_INSTRUMENT_MEM),
m_handlers(PSI_INSTRUMENT_MEM), m_records(PSI_INSTRUMENT_MEM),
m_children(PSI_INSTRUMENT_MEM), m_scope(scope)
{
init(prev->m_var_offset + prev->m_max_var_index,
prev->current_cursor_count(),
prev->get_num_case_exprs());
}
sp_pcontext::~sp_pcontext()
{
for (size_t i= 0; i < m_children.elements(); ++i)
delete m_children.at(i);
}
sp_pcontext *sp_pcontext::push_context(THD *thd, sp_pcontext::enum_scope scope)
{
sp_pcontext *child= new (thd->mem_root) sp_pcontext(this, scope);
if (child)
m_children.append(child);
return child;
}
bool cmp_labels(sp_label *a, sp_label *b)
{
return a->type == b->type && a->name.streq(b->name);
}
sp_pcontext *sp_pcontext::pop_context()
{
m_parent->m_max_var_index+= m_max_var_index;
uint submax= max_cursor_index();
if (submax > m_parent->m_max_cursor_index)
m_parent->m_max_cursor_index= submax;
if (m_num_case_exprs > m_parent->m_num_case_exprs)
m_parent->m_num_case_exprs= m_num_case_exprs;
/*
** Push unresolved goto label to parent context
*/
sp_label *label;
List_iterator_fast<sp_label> li(m_goto_labels);
while ((label= li++))
{
if (label->ip == 0)
{
m_parent->m_goto_labels.add_unique(label, &cmp_labels);
}
}
return m_parent;
}
uint sp_pcontext::diff_handlers(const sp_pcontext *ctx, bool exclusive) const
{
uint n= 0;
const sp_pcontext *pctx= this;
const sp_pcontext *last_ctx= NULL;
while (pctx && pctx != ctx)
{
n+= (uint)pctx->m_handlers.elements();
last_ctx= pctx;
pctx= pctx->parent_context();
}
if (pctx)
return (exclusive && last_ctx ? n -(uint) last_ctx->m_handlers.elements() : n);
return 0; // Didn't find ctx
}
uint sp_pcontext::diff_cursors(const sp_pcontext *ctx, bool exclusive) const
{
uint n= 0;
const sp_pcontext *pctx= this;
const sp_pcontext *last_ctx= NULL;
while (pctx && pctx != ctx)
{
n+= (uint)pctx->m_cursors.elements();
last_ctx= pctx;
pctx= pctx->parent_context();
}
if (pctx)
return (exclusive && last_ctx ? (uint)(n - last_ctx->m_cursors.elements()) : n);
return 0; // Didn't find ctx
}
sp_variable *sp_pcontext::find_variable(const LEX_CSTRING *name,
bool current_scope_only) const
{
size_t i= m_vars.elements() - m_pboundary;
while (i--)
{
sp_variable *p= m_vars.at(i);
if (p->name.streq(*name))
{
return p;
}
}
return (!current_scope_only && m_parent) ?
m_parent->find_variable(name, false) :
NULL;
}
/*
Find a variable by its run-time offset.
If the variable with a desired run-time offset is not found in this
context frame, it's recursively searched on parent context frames.
Note, context frames can have holes:
CREATE PROCEDURE p1() AS
x0 INT:=100;
CURSOR cur(p0 INT, p1 INT) IS SELECT p0, p1;
x1 INT:=101;
BEGIN
...
END;
The variables (x0 and x1) and the cursor parameters (p0 and p1)
reside in separate parse context frames.
The variables reside on the top level parse context frame:
- x0 has frame offset 0 and run-time offset 0
- x1 has frame offset 1 and run-time offset 3
The cursor parameters reside on the second level parse context frame:
- p0 has frame offset 0 and run-time offset 1
- p1 has frame offset 1 and run-time offset 2
Run-time offsets on a frame can have holes, but offsets monotonocally grow,
so run-time offsets of all variables are not greater than the run-time offset
of the very last variable in this frame.
*/
sp_variable *sp_pcontext::find_variable(uint offset) const
{
if (m_var_offset <= offset &&
m_vars.elements() &&
offset <= get_last_context_variable()->offset)
{
for (uint i= 0; i < m_vars.elements(); i++)
{
if (m_vars.at(i)->offset == offset)
return m_vars.at(i); // This frame
}
}
return m_parent ?
m_parent->find_variable(offset) : // Some previous frame
NULL; // Index out of bounds
}
uint sp_pcontext::default_context_var_count() const
{
uint default_params= 0;
for (uint i= 0; i< context_var_count(); i++)
{
sp_variable *spvar= get_context_variable(i);
if (!spvar)
break;
if (spvar->default_value)
default_params++;
}
return default_params;
}
sp_variable *sp_pcontext::add_variable(THD *thd, const LEX_CSTRING *name)
{
sp_variable *p=
new (thd->mem_root) sp_variable(name, m_var_offset + m_max_var_index);
if (!p)
return NULL;
++m_max_var_index;
return m_vars.append(p) ? NULL : p;
}
sp_label *sp_pcontext::push_label(THD *thd, const LEX_CSTRING *name, uint ip,
sp_label::enum_type type,
List<sp_label> *list)
{
sp_label *label=
new (thd->mem_root) sp_label(name, ip, type, this);
if (!label)
return NULL;
list->push_front(label, thd->mem_root);
return label;
}
sp_label *sp_pcontext::find_goto_label(const LEX_CSTRING *name, bool recusive)
{
List_iterator_fast<sp_label> li(m_goto_labels);
sp_label *lab;
while ((lab= li++))
{
if (lab->name.streq(*name))
return lab;
}
if (!recusive)
return NULL;
/*
Note about exception handlers.
See SQL:2003 SQL/PSM (ISO/IEC 9075-4:2003),
section 13.1 <compound statement>,
syntax rule 4.
In short, a DECLARE HANDLER block can not refer
to labels from the parent context, as they are out of scope.
*/
if (m_scope == HANDLER_SCOPE && m_parent)
{
if (m_parent->m_parent)
{
// Skip the parent context
return m_parent->m_parent->find_goto_label(name);
}
}
return m_parent && (m_scope == REGULAR_SCOPE) ?
m_parent->find_goto_label(name) :
NULL;
}
sp_label *sp_pcontext::find_label(const LEX_CSTRING *name)
{
List_iterator_fast<sp_label> li(m_labels);
sp_label *lab;
while ((lab= li++))
{
if (lab->name.streq(*name))
return lab;
}
/*
Note about exception handlers.
See SQL:2003 SQL/PSM (ISO/IEC 9075-4:2003),
section 13.1 <compound statement>,
syntax rule 4.
In short, a DECLARE HANDLER block can not refer
to labels from the parent context, as they are out of scope.
*/
return (m_parent && (m_scope == REGULAR_SCOPE)) ?
m_parent->find_label(name) :
NULL;
}
sp_label *sp_pcontext::find_label_current_loop_start()
{
List_iterator_fast<sp_label> li(m_labels);
sp_label *lab;
while ((lab= li++))
{
if (lab->type == sp_label::ITERATION)
return lab;
}
// See a comment in sp_pcontext::find_label()
return (m_parent && (m_scope == REGULAR_SCOPE)) ?
m_parent->find_label_current_loop_start() :
NULL;
}
bool sp_pcontext::add_condition(THD *thd,
const Lex_ident_column &name,
sp_condition_value *value)
{
sp_condition *p= new (thd->mem_root) sp_condition(name, value);
if (p == NULL)
return true;
return m_conditions.append(p);
}
sp_condition_value *sp_pcontext::find_condition(const LEX_CSTRING *name,
bool current_scope_only) const
{
size_t i= m_conditions.elements();
while (i--)
{
sp_condition *p= m_conditions.at(i);
if (p->eq_name(name))
{
return p->value;
}
}
return (!current_scope_only && m_parent) ?
m_parent->find_condition(name, false) :
NULL;
}
bool sp_pcontext::add_record(THD *thd, const Lex_ident_column &name,
Row_definition_list *field)
{
sp_record *p= new (thd->mem_root) sp_record(name, field);
if (p == NULL)
return true;
return m_records.append(p);
}
sp_record *sp_pcontext::find_record(const LEX_CSTRING *name,
bool current_scope_only) const
{
size_t i= m_records.elements();
while (i--)
{
sp_record *p= m_records.at(i);
if (p->eq_name(name))
{
return p;
}
}
return (!current_scope_only && m_parent) ?
m_parent->find_record(name, false) :
NULL;
}
sp_condition_value *
sp_pcontext::find_declared_or_predefined_condition(THD *thd,
const LEX_CSTRING *name)
const
{
sp_condition_value *p= find_condition(name, false);
if (p)
return p;
if (thd->variables.sql_mode & MODE_ORACLE)
return find_predefined_condition(name);
return NULL;
}
static sp_condition_value
// Warnings
cond_no_data_found(ER_SP_FETCH_NO_DATA, "01000"),
// Errors
cond_invalid_cursor(ER_SP_CURSOR_NOT_OPEN, "24000"),
cond_dup_val_on_index(ER_DUP_ENTRY, "23000"),
cond_dup_val_on_index2(ER_DUP_ENTRY_WITH_KEY_NAME, "23000"),
cond_too_many_rows(ER_TOO_MANY_ROWS, "42000");
static sp_condition sp_predefined_conditions[]=
{
// Warnings
sp_condition("NO_DATA_FOUND"_Lex_ident_column, &cond_no_data_found),
// Errors
sp_condition("INVALID_CURSOR"_Lex_ident_column, &cond_invalid_cursor),
sp_condition("DUP_VAL_ON_INDEX"_Lex_ident_column, &cond_dup_val_on_index),
sp_condition("DUP_VAL_ON_INDEX"_Lex_ident_column, &cond_dup_val_on_index2),
sp_condition("TOO_MANY_ROWS"_Lex_ident_column, &cond_too_many_rows)
};
sp_condition_value *
sp_pcontext::find_predefined_condition(const LEX_CSTRING *name) const
{
for (uint i= 0; i < array_elements(sp_predefined_conditions) ; i++)
{
if (sp_predefined_conditions[i].eq_name(name))
return sp_predefined_conditions[i].value;
}
return NULL;
}
sp_handler *sp_pcontext::add_handler(THD *thd,
sp_handler::enum_type type)
{
sp_handler *h= new (thd->mem_root) sp_handler(type);
if (!h)
return NULL;
return m_handlers.append(h) ? NULL : h;
}
bool sp_pcontext::check_duplicate_handler(
const sp_condition_value *cond_value) const
{
for (size_t i= 0; i < m_handlers.elements(); ++i)
{
sp_handler *h= m_handlers.at(i);
List_iterator_fast<sp_condition_value> li(h->condition_values);
sp_condition_value *cv;
while ((cv= li++))
{
if (cond_value->equals(cv))
return true;
}
}
return false;
}
bool sp_condition_value::matches(const Sql_condition_identity &value,
const sp_condition_value *found_cv) const
{
bool user_value_matched= !value.get_user_condition_value() ||
this == value.get_user_condition_value();
switch (type)
{
case sp_condition_value::ERROR_CODE:
return user_value_matched &&
value.get_sql_errno() == get_sql_errno() &&
(!found_cv || found_cv->type > sp_condition_value::ERROR_CODE);
case sp_condition_value::SQLSTATE:
return user_value_matched &&
Sql_state::eq(&value) &&
(!found_cv || found_cv->type > sp_condition_value::SQLSTATE);
case sp_condition_value::WARNING:
return user_value_matched &&
(value.Sql_state::is_warning() ||
value.get_level() == Sql_condition::WARN_LEVEL_WARN) &&
!found_cv;
case sp_condition_value::NOT_FOUND:
return user_value_matched &&
value.Sql_state::is_not_found() &&
!found_cv;
case sp_condition_value::EXCEPTION:
/*
In sql_mode=ORACLE this construct should catch both errors and warnings:
EXCEPTION
WHEN OTHERS THEN ...;
E.g. NO_DATA_FOUND is more like a warning than an error,
and it should be caught.
We don't check user_value_matched here.
"WHEN OTHERS" catches all user defined exception.
*/
return (((current_thd->variables.sql_mode & MODE_ORACLE) ||
(value.Sql_state::is_exception() &&
value.get_level() == Sql_condition::WARN_LEVEL_ERROR)) &&
!found_cv);
}
return false;
}
sp_handler*
sp_pcontext::find_handler(const Sql_condition_identity &value) const
{
sp_handler *found_handler= NULL;
sp_condition_value *found_cv= NULL;
for (size_t i= 0; i < m_handlers.elements(); ++i)
{
sp_handler *h= m_handlers.at(i);
List_iterator_fast<sp_condition_value> li(h->condition_values);
sp_condition_value *cv;
while ((cv= li++))
{
if (cv->matches(value, found_cv))
{
found_cv= cv;
found_handler= h;
}
}
}
if (found_handler)
return found_handler;
// There is no appropriate handler in this parsing context. We need to look up
// in parent contexts. There might be two cases here:
//
// 1. The current context has REGULAR_SCOPE. That means, it's a simple
// BEGIN..END block:
// ...
// BEGIN
// ... # We're here.
// END
// ...
// In this case we simply call find_handler() on parent's context recursively.
//
// 2. The current context has HANDLER_SCOPE. That means, we're inside an
// SQL-handler block:
// ...
// DECLARE ... HANDLER FOR ...
// BEGIN
// ... # We're here.
// END
// ...
// In this case we can not just call parent's find_handler(), because
// parent's handler don't catch conditions from this scope. Instead, we should
// try to find first parent context (we might have nested handler
// declarations), which has REGULAR_SCOPE (i.e. which is regular BEGIN..END
// block).
const sp_pcontext *p= this;
while (p && p->m_scope == HANDLER_SCOPE)
p= p->m_parent;
if (!p || !p->m_parent)
return NULL;
return p->m_parent->find_handler(value);
}
bool sp_pcontext::add_cursor(const LEX_CSTRING *name, sp_pcontext *param_ctx,
sp_lex_cursor *lex)
{
if (m_cursors.elements() == m_max_cursor_index)
++m_max_cursor_index;
return m_cursors.append(sp_pcursor(name, param_ctx, lex));
}
const sp_pcursor *sp_pcontext::find_cursor(const LEX_CSTRING *name,
uint *poff,
bool current_scope_only) const
{
uint i= (uint)m_cursors.elements();
while (i--)
{
if (m_cursors.at(i).streq(*name))
{
*poff= m_cursor_offset + i;
return &m_cursors.at(i);
}
}
return (!current_scope_only && m_parent) ?
m_parent->find_cursor(name, poff, false) :
NULL;
}
void sp_pcontext::retrieve_field_definitions(
List<Spvar_definition> *field_def_lst) const
{
/* Put local/context fields in the result list. */
size_t next_child= 0;
for (size_t i= 0; i < m_vars.elements(); ++i)
{
sp_variable *var_def= m_vars.at(i);
/*
The context can have holes in run-time offsets,
the missing offsets reside on the children contexts in such cases.
Example:
CREATE PROCEDURE p1() AS
x0 INT:=100; -- context 0, position 0, run-time 0
CURSOR cur(
p0 INT, -- context 1, position 0, run-time 1
p1 INT -- context 1, position 1, run-time 2
) IS SELECT p0, p1;
x1 INT:=101; -- context 0, position 1, run-time 3
BEGIN
...
END;
See more comments in sp_pcontext::find_variable().
We must retrieve the definitions in the order of their run-time offsets.
Check that there are children that should go before the current variable.
*/
for ( ; next_child < m_children.elements(); next_child++)
{
sp_pcontext *child= m_children.at(next_child);
if (!child->context_var_count() ||
child->get_context_variable(0)->offset > var_def->offset)
break;
/*
All variables on the embedded context (that fills holes of the parent)
should have the run-time offset strictly less than var_def.
*/
DBUG_ASSERT(child->get_context_variable(0)->offset < var_def->offset);
DBUG_ASSERT(child->get_last_context_variable()->offset < var_def->offset);
child->retrieve_field_definitions(field_def_lst);
}
field_def_lst->push_back(&var_def->field_def);
}
/* Put the fields of the remaining enclosed contexts in the result list. */
for (size_t i= next_child; i < m_children.elements(); ++i)
m_children.at(i)->retrieve_field_definitions(field_def_lst);
}
const sp_pcursor *sp_pcontext::find_cursor(uint offset) const
{
if (m_cursor_offset <= offset &&
offset < m_cursor_offset + m_cursors.elements())
{
return &m_cursors.at(offset - m_cursor_offset); // This frame
}
return m_parent ?
m_parent->find_cursor(offset) : // Some previous frame
NULL; // Index out of bounds
}
bool sp_pcursor::check_param_count_with_error(uint param_count) const
{
if (param_count != (m_param_context ?
m_param_context->context_var_count() : 0))
{
my_error(ER_WRONG_PARAMCOUNT_TO_CURSOR, MYF(0), LEX_CSTRING::str);
return true;
}
return false;
}
const Spvar_definition *
sp_variable::find_row_field(const LEX_CSTRING *var_name,
const LEX_CSTRING *field_name,
uint *row_field_offset)
{
if (!field_def.is_row())
{
my_printf_error(ER_UNKNOWN_ERROR,
"'%s' is not a row variable", MYF(0), var_name->str);
return NULL;
}
const Spvar_definition *def;
if ((def= field_def.find_row_field_by_name(field_name, row_field_offset)))
return def;
my_error(ER_ROW_VARIABLE_DOES_NOT_HAVE_FIELD, MYF(0),
var_name->str, field_name->str);
return NULL;
}