diff options
Diffstat (limited to 'sql/sp_pcontext.cc')
-rw-r--r-- | sql/sp_pcontext.cc | 742 |
1 files changed, 742 insertions, 0 deletions
diff --git a/sql/sp_pcontext.cc b/sql/sp_pcontext.cc new file mode 100644 index 00000000..848d1f0c --- /dev/null +++ b/sql/sp_pcontext.cc @@ -0,0 +1,742 @@ +/* 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" +#ifdef USE_PRAGMA_IMPLEMENTATION +#pragma implementation +#endif + +#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_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_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 (lex_string_cmp(system_charset_info, &a->name, &b->name) == 0 && + a->type == b->type); +} + +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 (system_charset_info->strnncoll(name->str, name->length, + p->name.str, p->name.length) == 0) + { + 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 +} + + +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 (lex_string_cmp(system_charset_info, name, &lab->name) == 0) + 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 (lex_string_cmp(system_charset_info, name, &lab->name) == 0) + 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_CSTRING *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; +} + +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(STRING_WITH_LEN("NO_DATA_FOUND"), &cond_no_data_found), + // Errors + sp_condition(STRING_WITH_LEN("INVALID_CURSOR"), &cond_invalid_cursor), + sp_condition(STRING_WITH_LEN("DUP_VAL_ON_INDEX"), &cond_dup_val_on_index), + sp_condition(STRING_WITH_LEN("DUP_VAL_ON_INDEX"), &cond_dup_val_on_index2), + sp_condition(STRING_WITH_LEN("TOO_MANY_ROWS"), &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--) + { + LEX_CSTRING n= m_cursors.at(i); + + if (system_charset_info->strnncoll(name->str, name->length, + n.str, n.length) == 0) + { + *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; +} |