Skip to content

MDEV-39022 Add LOCAL spvar syntax for prepared statements and SYS_R…#5146

Open
sanja-byelkin wants to merge 1 commit into
mainfrom
bb-13.0-bar-MDEV-39022
Open

MDEV-39022 Add LOCAL spvar syntax for prepared statements and SYS_R…#5146
sanja-byelkin wants to merge 1 commit into
mainfrom
bb-13.0-bar-MDEV-39022

Conversation

@sanja-byelkin
Copy link
Copy Markdown
Member

…EFCURSORs

This patch adds the following syntax:

OPEN c0 FOR LOCAL spvar_with_ps_name;
PREPARE LOCAL spvar_with_ps_name FROM 'dynamic sql'; EXECUTE LOCAL spvar_with_ps_name;
DEALLOCATE PREPARE LOCAL spvar_with_ps_name;

OPEN c0 FOR PREPARE stmt;

…EFCURSORs

This patch adds the following syntax:

OPEN c0 FOR LOCAL spvar_with_ps_name;
PREPARE LOCAL spvar_with_ps_name FROM 'dynamic sql';
EXECUTE LOCAL spvar_with_ps_name;
DEALLOCATE PREPARE LOCAL spvar_with_ps_name;

OPEN c0 FOR PREPARE stmt;
@sanja-byelkin sanja-byelkin requested a review from Copilot May 29, 2026 12:05
@CLAassistant
Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request implements support for opening cursors using prepared statements and local variables (e.g., 'OPEN c FOR PREPARE stmt' and 'OPEN c FOR LOCAL spvar'). The feedback identifies critical security and stability issues, including potential null pointer dereferences when executing prepared statements with local variables outside of stored procedures, and a potential out-of-bounds read due to non-null-terminated LEX_CSTRING variables. Additionally, it is recommended to introduce an 'is_ps' boolean flag to prevent incorrect fallbacks when statement names evaluate to empty, and to add clarifying comments for temporary string buffers.

Comment thread sql/sql_prepare.cc
Comment on lines +2819 to +2822
if (name.local_var_name().length)
{
sp_variable *spvar= lex->spcont->find_variable_or_error(
name.local_var_name(), false);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-critical critical

If PREPARE LOCAL spvar FROM ... is executed as a top-level statement outside of a stored procedure, lex->spcont will be nullptr. Accessing lex->spcont->find_variable_or_error without a null check will cause a server crash. We must add a check to ensure lex->spcont is not null before attempting to look up the local variable.

  if (name.local_var_name().length)
  {
    if (!lex->spcont)
    {
      my_error(ER_SP_UNDECLARED_VAR, MYF(0), name.local_var_name().str);
      return true;
    }
    sp_variable *spvar= lex->spcont->find_variable_or_error(
                                                name.local_var_name(), false);

Comment thread sql/sp_instr.cc
Comment on lines +2647 to +2661
StringBuffer<64> value_buffer, converter_buffer;
Lex_ident_sys ps_name= lps.evaluate_name(thd, &value_buffer,
&converter_buffer, "OPEN");
if (ps_name.str == nullptr && lps.var().rcontext_handler()/*FOR LOCAL*/)
{
/*
We're here if this is an "OPEN c0 FOR LOCAL spvar" statement
and the SP variable evaluates to SQL NULL:
SET spvar= NULL;
OPEN c0 FOR LOCAL spvar;
evaluate_name() should have raised the ER_UNKNOWN_STMT_HANDLER error.
*/
DBUG_ASSERT(thd->is_error());
DBUG_RETURN(-1);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

We should evaluate whether the cursor is a prepared-statement-based cursor (either a simple PS or a LOCAL variable) and store this in a boolean variable is_ps. This prevents us from incorrectly falling back to a regular cursor open or dynamic string open when the evaluated statement name is empty (e.g., when spvar is ''). Also, since value_buffer and converter_buffer are String objects initialized with temporary buffers whose charsets are expected to be immediately overwritten, we should add a comment indicating this.

  StringBuffer<64> value_buffer, converter_buffer; // charset will be overwritten
  Lex_ident_sys ps_name= lps.evaluate_name(thd, &value_buffer,
                                           &converter_buffer, "OPEN");
  bool is_ps= lps.var().rcontext_handler() != nullptr || lps.name().str != nullptr;
  if (ps_name.str == nullptr && lps.var().rcontext_handler()/*FOR LOCAL*/)
  {
    /*
      We're here if this is an "OPEN c0 FOR LOCAL spvar" statement
      and the SP variable evaluates to SQL NULL:
        SET spvar= NULL;
        OPEN c0 FOR LOCAL spvar;
      evaluate_name() should have raised the ER_UNKNOWN_STMT_HANDLER error.
    */
    DBUG_ASSERT(thd->is_error());
    DBUG_RETURN(-1);
  }
References
  1. When a String object is initialized with a temporary buffer and its charset is expected to be immediately overwritten (e.g., by a subsequent call to field->val_str()), add a comment such as '// charset will be overwritten' to prevent confusion for future developers.

Comment thread sql/sp_instr.cc
Comment on lines +2692 to +2696
int rc= ps_name.length ? cursor->open_from_ps(thd, ps_name) :
!lps.code() ? cursor->open(thd, m_cursor_name, return_type) :
cursor->open_from_dynamic_string(thd,
set_placeholder_first_ip,
m_set_ps_placeholder_count);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Use the is_ps boolean to determine whether to open the cursor from a prepared statement, rather than checking ps_name.length. This ensures that if the evaluated statement name is empty, we still attempt to open it as a prepared statement (which will correctly fail with an unknown statement handler error) instead of falling back to opening it as a regular cursor or dynamic string.

    int rc= is_ps ? cursor->open_from_ps(thd, ps_name) :
            !lps.code() ? cursor->open(thd, m_cursor_name, return_type) :
                          cursor->open_from_dynamic_string(thd,
                                                set_placeholder_first_ip,
                                                m_set_ps_placeholder_count);

Comment thread sql/sp_instr.cc
Comment on lines +2720 to 2724
DBUG_RETURN(ps_name.length ? cursor->open_from_ps(thd, ps_name) :
!lps.code() ? cursor->open(thd, m_cursor_name, return_type,
false/*don't check max_open_cursors*/) :
cursor->open_from_dynamic_string(thd, set_placeholder_first_ip,
m_set_ps_placeholder_count));
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Use the is_ps boolean here as well to ensure consistent behavior when reopening the cursor.

  DBUG_RETURN(is_ps ? cursor->open_from_ps(thd, ps_name) :
              !lps.code() ? cursor->open(thd, m_cursor_name, return_type,
                                       false/*don't check max_open_cursors*/) :
              cursor->open_from_dynamic_string(thd, set_placeholder_first_ip,
                                               m_set_ps_placeholder_count));

Comment thread sql/sql_prepare.cc
Comment on lines +2842 to +2845
if (open_for.local_var_name().length)
{
sp_variable *spvar= lex->spcont->find_variable_or_error(
open_for.local_var_name(), false);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-high high

For defensive programming and to prevent potential crashes in release builds (where DBUG_ASSERT(lex->spcont) is compiled out), we should also check if lex->spcont is null before calling find_variable_or_error in this overload.

  if (open_for.local_var_name().length)
  {
    if (!lex->spcont)
    {
      my_error(ER_SP_UNDECLARED_VAR, MYF(0), open_for.local_var_name().str);
      return true;
    }
    sp_variable *spvar= lex->spcont->find_variable_or_error(
                                             open_for.local_var_name(), false);

Comment thread sql/sp_pcontext.h
Comment on lines +543 to +550
sp_variable *find_variable_or_error(const LEX_CSTRING &name,
bool current_scope_only) const
{
sp_variable *res= find_variable(&name, current_scope_only);
if (!res)
my_error(ER_SP_UNDECLARED_VAR, MYF(0), name.str);
return res;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-high high

LEX_CSTRING is not guaranteed to be null-terminated (it is often a slice of the query buffer). Passing name.str directly to my_error with the %s format specifier can result in printing garbage or reading out of bounds. We should ensure the name is safely null-terminated before passing it to my_error.

  sp_variable *find_variable_or_error(const LEX_CSTRING &name,
                                      bool current_scope_only) const
  {
    sp_variable *res= find_variable(&name, current_scope_only);
    if (!res)
    {
      char buf[128];
      size_t len= name.length < sizeof(buf) ? name.length : sizeof(buf) - 1;
      memcpy(buf, name.str, len);
      buf[len]= '\0';
      my_error(ER_SP_UNDECLARED_VAR, MYF(0), buf);
    }
    return res;
  }

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

Adds support for using LOCAL SP variables as prepared statement names, and for opening cursor variables from prepared statements (including via LOCAL variables), aligning behavior with ORACLE-mode SYS_REFCURSOR usage.

Changes:

  • Extend the SQL grammar to accept PREPARE/EXECUTE/DEALLOCATE PREPARE LOCAL spvar and OPEN c FOR PREPARE stmt / OPEN c FOR LOCAL spvar.
  • Add runtime support to resolve prepared-statement names from SP local variables (including erroring when the variable evaluates to SQL NULL).
  • Add mysql-test coverage for the new syntax and cursor open behaviors (including disallowed cases for strict ref cursors and stored functions).

Reviewed changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
sql/sql_yacc.yy Extends parser rules/types to accept LOCAL statement names and enhanced OPEN ... FOR ... forms.
sql/sql_prepare.cc Implements variable-based statement-name resolution and wires it into PREPARE/EXECUTE/DEALLOCATE execution paths.
sql/sql_lex.h Introduces Lex_sql_statement_name_st and Lex_open_for_st, and extends Lex_prepared_stmt to store SP-variable address for LOCAL.
sql/sql_lex.cc Updates statement builders and OPEN cursor handling to support PREPARE and LOCAL sources, plus validation/error paths.
sql/sp_pcontext.h Adds find_variable_or_error() helper to consolidate SP-variable lookup + error reporting.
sql/sp_instr.cc Updates cursor-open instruction execution to resolve prepared statement name at runtime (including LOCAL variable evaluation).
mysql-test/main/sp-cursor-for-ps.test New tests for OPEN ... FOR PREPARE and OPEN ... FOR LOCAL, including error cases.
mysql-test/main/sp-cursor-for-ps.result Expected output for the new sp-cursor-for-ps test.
mysql-test/main/ps-local.test New tests for PREPARE/EXECUTE/DEALLOCATE ... LOCAL and mixed simple/extended usages.
mysql-test/main/ps-local.result Expected output for the new ps-local test.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread sql/sql_prepare.cc
Comment on lines +2809 to +2851
bool Lex_prepared_stmt::set(LEX *lex, const Lex_sql_statement_name_st &name,
Item *code, List<Item> *params)
{
DBUG_ASSERT(m_params.elements == 0);
// Simple and extended prepared statement name cannot co-exist
DBUG_ASSERT(!name.ps_name().length || !name.local_var_name().length);
if (params)
m_params= *params;
m_code= code;
m_name= name.ps_name();
if (name.local_var_name().length)
{
sp_variable *spvar= lex->spcont->find_variable_or_error(
name.local_var_name(), false);
if (!spvar)
return true;
m_var= sp_rcontext_addr(&sp_rcontext_handler_local, spvar->offset);
}
return false;
}


bool Lex_prepared_stmt::set(LEX *lex, const Lex_open_for_st &open_for,
List<Item> *params)
{
DBUG_ASSERT(m_params.elements == 0);
DBUG_ASSERT(lex->spcont);
// Simple and extended prepared statement name cannot co-exist
DBUG_ASSERT(!open_for.ps_name().length || !open_for.local_var_name().length);
if (params)
m_params= *params;
m_name= open_for.ps_name();
m_code= open_for.dynamic_string();
if (open_for.local_var_name().length)
{
sp_variable *spvar= lex->spcont->find_variable_or_error(
open_for.local_var_name(), false);
if (!spvar)
return true;
m_var= sp_rcontext_addr(&sp_rcontext_handler_local, spvar->offset);
}
return false;
}
Comment thread sql/sql_yacc.yy
Comment on lines +3800 to +3808
open_for:
select_for_open_cursor // OPEN c FOR SELECT 1;
{
$$= Lex_open_for_st::make_for_query();
}
| bit_expr // OPEN c FOR 'SELECT 1';
{
$$= Lex_open_for_st::make_for_dynamic_string($1);
}
Comment thread sql/sql_lex.cc
if (stmt->prepared_stmt.code())

/*
It can be either of this:
Comment thread sql/sql_lex.cc
Comment on lines +7893 to +7894
It can be rewritten to use a weak cursor variable:
OPEN weak_cursor_variable FORM PREPARE stmt;
Comment thread sql/sql_lex.cc
Comment on lines +7905 to +7906
It can be rewritten to use a weak cursor variable:
OPEN weak_cursor_variable FORM LOCAL ps_name_variable;
Copy link
Copy Markdown
Member Author

@sanja-byelkin sanja-byelkin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generally all is good, but fix small things.

Missing coverage:

  • EXECUTE LOCAL spvar USING — the USING parameter path is never exercised with the LOCAL form. The test only covers the "not all parameters bound" error case.
  • PREPARE LOCAL spvar where spvar holds NULL (error path) — covered for OPEN but not for standalone PREPARE/EXECUTE/DEALLOCATE.
  • OPEN c FOR LOCAL spvar inside a trigger — only stored functions are tested for the "not allowed" path;

Comment thread sql/sql_lex.cc
This statement is not supported:
OPEN strict_cursor_variable FOR PREPARE stmt;
It can be rewritten to use a weak cursor variable:
OPEN weak_cursor_variable FORM PREPARE stmt;
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FORM -> FOR

Comment thread sql/sql_lex.cc
This statement is not supported:
OPEN strict_cursor_variable FOR LOCAL ps_name_variable;
It can be rewritten to use a weak cursor variable:
OPEN weak_cursor_variable FORM LOCAL ps_name_variable;
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FORM -> FOR

Comment thread sql/sql_lex.h
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

may be reset m_var here?

or reset it in set where it set conditionally.

Comment thread sql/sql_prepare.cc
Item *item= thd->get_variable(m_var);
if (!(item= thd->sp_fix_func_item(&item)))
return Lex_ident_sys();

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

here if item is NULL so there was an error. Why it after it return return Lex_ident_sys(tmp.str, tmp.length); as if nothing happened. If it is error handling please put comment here.

Comment thread sql/sql_lex.h
Lex_open_for_st res;
res.m_type= QUERY;
res.m_name= Lex_ident_sys();
res.m_dynamic_string= nullptr;
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  union
  {
    /*
      m_name contains either PS name or an SP variable name:
        OPEN c FOR PREPARE stmt;
        OPEN c FOR LOCAL spvar;
    */
    Lex_ident_sys_st m_name;
    Item *m_dynamic_string;      // OPEN c FOR expr;
  };

so both part of union is assigned

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

4 participants