diff --git a/mysql-test/main/ps-local.result b/mysql-test/main/ps-local.result new file mode 100644 index 0000000000000..fa7f1ff7e70f8 --- /dev/null +++ b/mysql-test/main/ps-local.result @@ -0,0 +1,74 @@ +SET NAMES utf8mb3; +SET sql_mode=ORACLE; +CREATE PROCEDURE p1 AS +TYPE rec0_t IS RECORD (a INT, b INT); +TYPE cur0_t IS REF CURSOR RETURN rec0_t; +c0 cur0_t; +ps_name VARCHAR(32) DEFAULT 'stmt'; +BEGIN +PREPARE stmt FROM 'SELECT 1,2'; +OPEN c0 FOR LOCAL ps_name; +END; +$$ +ERROR HY000: Incorrect usage of c0 and OPEN..FOR LOCAL ps_name_variable +SET sql_mode=DEFAULT; +CREATE PROCEDURE p1() +BEGIN +DECLARE spvar VARCHAR(32) DEFAULT 'stmt'; +PREPARE LOCAL spvar FROM 'SELECT 1 AS c0'; +EXECUTE LOCAL spvar; +DEALLOCATE PREPARE LOCAL spvar; +END; +$$ +CALL p1; +c0 +1 +DROP PROCEDURE p1; +CREATE PROCEDURE p1() +BEGIN +DECLARE spvar VARCHAR(32) DEFAULT 'stmt'; +PREPARE stmt FROM 'SELECT 1 AS c0'; +EXECUTE LOCAL spvar; +DEALLOCATE PREPARE LOCAL spvar; +END; +$$ +CALL p1; +c0 +1 +DROP PROCEDURE p1; +CREATE PROCEDURE p1() +BEGIN +DECLARE spvar VARCHAR(32) DEFAULT 'stmt'; +PREPARE LOCAL spvar FROM 'SELECT 1 AS c0'; +EXECUTE stmt; +DEALLOCATE PREPARE LOCAL spvar; +END; +$$ +CALL p1; +c0 +1 +DROP PROCEDURE p1; +CREATE PROCEDURE p1() +BEGIN +DECLARE spvar VARCHAR(32) DEFAULT 'stmt'; +PREPARE LOCAL spvar FROM 'SELECT 1 AS c0'; +EXECUTE LOCAL spvar; +DEALLOCATE PREPARE stmt; +END; +$$ +CALL p1; +c0 +1 +DROP PROCEDURE p1; +CREATE PROCEDURE p1() +BEGIN +DECLARE spvar VARCHAR(32) CHARACTER SET latin1 DEFAULT 'àçé'; +PREPARE LOCAL spvar FROM 'SELECT 1 AS c0'; +EXECUTE LOCAL spvar; +DEALLOCATE PREPARE LOCAL spvar; +END; +$$ +CALL p1; +c0 +1 +DROP PROCEDURE p1; diff --git a/mysql-test/main/ps-local.test b/mysql-test/main/ps-local.test new file mode 100644 index 0000000000000..aa39c8ae9a360 --- /dev/null +++ b/mysql-test/main/ps-local.test @@ -0,0 +1,95 @@ +SET NAMES utf8mb3; + +# OPEN strict_cursor_variable FROM LOCAL ps_name_variable -- not supported +SET sql_mode=ORACLE; +DELIMITER $$; +--error ER_WRONG_USAGE +CREATE PROCEDURE p1 AS + TYPE rec0_t IS RECORD (a INT, b INT); + TYPE cur0_t IS REF CURSOR RETURN rec0_t; + c0 cur0_t; + ps_name VARCHAR(32) DEFAULT 'stmt'; +BEGIN + PREPARE stmt FROM 'SELECT 1,2'; + OPEN c0 FOR LOCAL ps_name; +END; +$$ +DELIMITER ;$$ +SET sql_mode=DEFAULT; + + +# PREPARE extended; EXECUTE extended; DEALLOCATE extended; + +DELIMITER $$; +CREATE PROCEDURE p1() +BEGIN + DECLARE spvar VARCHAR(32) DEFAULT 'stmt'; + PREPARE LOCAL spvar FROM 'SELECT 1 AS c0'; + EXECUTE LOCAL spvar; + DEALLOCATE PREPARE LOCAL spvar; +END; +$$ +DELIMITER ;$$ +CALL p1; +DROP PROCEDURE p1; + +# PREPARE simple; EXECUTE extended; DEALLOCATE extended; + +DELIMITER $$; +CREATE PROCEDURE p1() +BEGIN + DECLARE spvar VARCHAR(32) DEFAULT 'stmt'; + PREPARE stmt FROM 'SELECT 1 AS c0'; + EXECUTE LOCAL spvar; + DEALLOCATE PREPARE LOCAL spvar; +END; +$$ +DELIMITER ;$$ +CALL p1; +DROP PROCEDURE p1; + +# PREPARE extended; EXECUTE simple; DEALLOCATE extended; + +DELIMITER $$; +CREATE PROCEDURE p1() +BEGIN + DECLARE spvar VARCHAR(32) DEFAULT 'stmt'; + PREPARE LOCAL spvar FROM 'SELECT 1 AS c0'; + EXECUTE stmt; + DEALLOCATE PREPARE LOCAL spvar; +END; +$$ +DELIMITER ;$$ +CALL p1; +DROP PROCEDURE p1; + +# PREPARE extended; EXECUTE extended; DEALLOCATE simple; + +DELIMITER $$; +CREATE PROCEDURE p1() +BEGIN + DECLARE spvar VARCHAR(32) DEFAULT 'stmt'; + PREPARE LOCAL spvar FROM 'SELECT 1 AS c0'; + EXECUTE LOCAL spvar; + DEALLOCATE PREPARE stmt; +END; +$$ +DELIMITER ;$$ +CALL p1; +DROP PROCEDURE p1; + + +# Extended name with non-ASCII characters + +DELIMITER $$; +CREATE PROCEDURE p1() +BEGIN + DECLARE spvar VARCHAR(32) CHARACTER SET latin1 DEFAULT 'àçé'; + PREPARE LOCAL spvar FROM 'SELECT 1 AS c0'; + EXECUTE LOCAL spvar; + DEALLOCATE PREPARE LOCAL spvar; +END; +$$ +DELIMITER ;$$ +CALL p1; +DROP PROCEDURE p1; diff --git a/mysql-test/main/sp-cursor-for-ps.result b/mysql-test/main/sp-cursor-for-ps.result new file mode 100644 index 0000000000000..5222379a820f4 --- /dev/null +++ b/mysql-test/main/sp-cursor-for-ps.result @@ -0,0 +1,88 @@ +SET NAMES utf8mb3; +SET sql_mode=ORACLE; +CREATE PROCEDURE p1 AS +TYPE rec0_t IS RECORD (a INT, b INT); +TYPE cur0_t IS REF CURSOR RETURN rec0_t; +c0 cur0_t; +BEGIN +PREPARE stmt FROM 'SELECT 1,2'; +OPEN c0 FOR PREPARE stmt; +END; +$$ +ERROR HY000: Incorrect usage of c0 and OPEN..FOR PREPARE ps_name +CREATE PROCEDURE p1 AS +v0 TEXT; +c0 SYS_REFCURSOR; +BEGIN +PREPARE stmt FROM 'SELECT 123'; +OPEN c0 FOR PREPARE stmt; +FETCH c0 INTO v0; +CLOSE c0; +SELECT v0; +END; +$$ +CALL p1; +v0 +123 +DROP PROCEDURE p1; +CREATE FUNCTION f1() RETURN INT AS +c0 SYS_REFCURSOR; +BEGIN +OPEN c0 FOR PREPARE stmt; +RETURN 0; +END; +$$ +ERROR 0A000: Dynamic SQL is not allowed in stored function or trigger +CREATE PROCEDURE p1 AS +c0 SYS_REFCURSOR; +v0 TEXT; +ps_name VARCHAR(30) := 'stmt'; +BEGIN +PREPARE stmt FROM 'SELECT 10'; +OPEN c0 FOR LOCAL ps_name; +FETCH c0 INTO v0; +CLOSE c0; +SELECT v0; +END; +$$ +CALL p1; +v0 +10 +DROP PROCEDURE p1; +CREATE FUNCTION f1() RETURN INT AS +c0 SYS_REFCURSOR; +spvar VARCHAR(10) DEFAULT 'stmt0'; +BEGIN +OPEN c0 FOR LOCAL spvar; +RETURN 0; +END; +$$ +ERROR 0A000: Dynamic SQL is not allowed in stored function or trigger +CREATE PROCEDURE p1 AS +c0 SYS_REFCURSOR; +ps_name VARCHAR(10) := 'stmt'; +BEGIN +PREPARE stmt FROM 'SELECT :a'; +OPEN c0 FOR LOCAL ps_name; +END; +$$ +CALL p1; +ERROR HY000: Incorrect arguments to OPEN +DROP PROCEDURE p1; +CREATE PROCEDURE p1 AS +c0 SYS_REFCURSOR; +v0 TEXT; +ps_name VARCHAR(30) := 'àçé'; +BEGIN +PREPARE àçé FROM 'SELECT 10'; +OPEN c0 FOR LOCAL ps_name; +FETCH c0 INTO v0; +CLOSE c0; +SELECT v0; +DEALLOCATE PREPARE àçé; +END; +$$ +CALL p1; +v0 +10 +DROP PROCEDURE p1; diff --git a/mysql-test/main/sp-cursor-for-ps.test b/mysql-test/main/sp-cursor-for-ps.test new file mode 100644 index 0000000000000..065d6196c6cb0 --- /dev/null +++ b/mysql-test/main/sp-cursor-for-ps.test @@ -0,0 +1,121 @@ +SET NAMES utf8mb3; +SET sql_mode=ORACLE; + +# OPEN strict_cursor_variable FOR PREPARE stmt - not supported + +DELIMITER $$; +--error ER_WRONG_USAGE +CREATE PROCEDURE p1 AS + TYPE rec0_t IS RECORD (a INT, b INT); + TYPE cur0_t IS REF CURSOR RETURN rec0_t; + c0 cur0_t; +BEGIN + PREPARE stmt FROM 'SELECT 1,2'; + OPEN c0 FOR PREPARE stmt; +END; +$$ +DELIMITER ;$$ + + +# OPEN c FOR PREPARE stmt - a working example + +DELIMITER $$; +CREATE PROCEDURE p1 AS + v0 TEXT; + c0 SYS_REFCURSOR; +BEGIN + PREPARE stmt FROM 'SELECT 123'; + OPEN c0 FOR PREPARE stmt; + FETCH c0 INTO v0; + CLOSE c0; + SELECT v0; +END; +$$ +DELIMITER ;$$ +CALL p1; +DROP PROCEDURE p1; + + +# OPEN c FOR PREPARE stmt - not allowed in a stored function + +DELIMITER $$; +--error ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG +CREATE FUNCTION f1() RETURN INT AS + c0 SYS_REFCURSOR; +BEGIN + OPEN c0 FOR PREPARE stmt; + RETURN 0; +END; +$$ +DELIMITER ;$$ + + +# OPEN c FOR LOCAL var + +DELIMITER $$; +CREATE PROCEDURE p1 AS + c0 SYS_REFCURSOR; + v0 TEXT; + ps_name VARCHAR(30) := 'stmt'; +BEGIN + PREPARE stmt FROM 'SELECT 10'; + OPEN c0 FOR LOCAL ps_name; + FETCH c0 INTO v0; + CLOSE c0; + SELECT v0; +END; +$$ +DELIMITER ;$$ +CALL p1; +DROP PROCEDURE p1; + + +# OPEN c FOR LOCAL spvar - not allowed in a stored function +DELIMITER $$; +--error ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG +CREATE FUNCTION f1() RETURN INT AS + c0 SYS_REFCURSOR; + spvar VARCHAR(10) DEFAULT 'stmt0'; +BEGIN + OPEN c0 FOR LOCAL spvar; + RETURN 0; +END; +$$ +DELIMITER ;$$ + + +# OPEN c FOR LOCAL var - not all parameters bound +DELIMITER $$; +CREATE PROCEDURE p1 AS + c0 SYS_REFCURSOR; + ps_name VARCHAR(10) := 'stmt'; +BEGIN + PREPARE stmt FROM 'SELECT :a'; + OPEN c0 FOR LOCAL ps_name; +END; +$$ +DELIMITER ;$$ +--error ER_WRONG_ARGUMENTS +CALL p1; +DROP PROCEDURE p1; + + +# OPEN c FOR LOCAL var - non-ascii characters in PS name + +DELIMITER $$; +CREATE PROCEDURE p1 AS + c0 SYS_REFCURSOR; + v0 TEXT; + ps_name VARCHAR(30) := 'àçé'; +BEGIN + PREPARE àçé FROM 'SELECT 10'; + OPEN c0 FOR LOCAL ps_name; + FETCH c0 INTO v0; + CLOSE c0; + SELECT v0; + DEALLOCATE PREPARE àçé; +END; +$$ +DELIMITER ;$$ +CALL p1; +DROP PROCEDURE p1; diff --git a/sql/sp_instr.cc b/sql/sp_instr.cc index 113ba079147f8..ec752fd1b51c6 100644 --- a/sql/sp_instr.cc +++ b/sql/sp_instr.cc @@ -2634,8 +2634,8 @@ sp_instr_copen_by_ref::execute(THD *thd, uint *nextp) int sp_instr_copen_by_ref::exec_core(THD *thd, uint *nextp) { DBUG_ENTER("sp_instr_copen_by_ref::exec_core"); - const Item *code= m_lex_keeper.lex()->get_lex_for_cursor()-> - prepared_stmt.code(); + const Lex_prepared_stmt &lps= m_lex_keeper.lex()->get_lex_for_cursor()-> + prepared_stmt; DBUG_ASSERT(m_ip >= m_set_ps_placeholder_count); const sp_instr_set_ps_placeholder *set_placeholder_first= dynamic_cast( @@ -2643,6 +2643,23 @@ int sp_instr_copen_by_ref::exec_core(THD *thd, uint *nextp) DBUG_ASSERT(set_placeholder_first || !m_set_ps_placeholder_count); uint set_placeholder_first_ip= set_placeholder_first ? set_placeholder_first->m_ip : 0; + + 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); + } + sp_cursor *cursor; /* @@ -2672,10 +2689,11 @@ int sp_instr_copen_by_ref::exec_core(THD *thd, uint *nextp) // TODO: check with DmitryS if hiding ROOT_FLAG_READ_ONLY is OK: auto flags_backup= thd->lex->query_arena()->mem_root->flags; thd->lex->query_arena()->mem_root->flags&= ~ROOT_FLAG_READ_ONLY; - int rc= !code ? cursor->open(thd, m_cursor_name, return_type) : - cursor->open_from_dynamic_string(thd, - set_placeholder_first_ip, - m_set_ps_placeholder_count); + 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); thd->lex->query_arena()->mem_root->flags= flags_backup; DBUG_RETURN(rc); } @@ -2699,8 +2717,9 @@ int sp_instr_copen_by_ref::exec_core(THD *thd, uint *nextp) DBUG_RETURN(-1); } cursor->reset_for_reopen(thd); - DBUG_RETURN(!code ? cursor->open(thd, m_cursor_name, return_type, - false/*don't check max_open_cursors*/) : + 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)); } diff --git a/sql/sp_pcontext.h b/sql/sp_pcontext.h index 421b708b9fa9d..a178bee41c6e1 100644 --- a/sql/sp_pcontext.h +++ b/sql/sp_pcontext.h @@ -539,6 +539,16 @@ class sp_pcontext : public Sql_alloc, /// @return instance of found SP-variable, or NULL if not found. sp_variable *find_variable(const LEX_CSTRING *name, bool current_scope_only) const; + /// Find an SP-variable by name, raise an error if not found + 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; + } + /// Find SP-variable by the offset in the root parsing context. /// /// The function is used for two things: diff --git a/sql/sql_lex.cc b/sql/sql_lex.cc index e1c7397fa6d94..12087fc4919b2 100644 --- a/sql/sql_lex.cc +++ b/sql/sql_lex.cc @@ -7820,6 +7820,7 @@ bool LEX::sp_open_cursor_for_stmt(THD *thd, const LEX_CSTRING *name, sp_variable *spv; const sp_type_def_ref* return_type_def; const Row_definition_list *row_def_list; + uint dynamic_count; if (stmt->prepared_stmt.code() && stmt->stmt_prepare_validate("OPEN..FOR")) @@ -7844,30 +7845,70 @@ bool LEX::sp_open_cursor_for_stmt(THD *thd, const LEX_CSTRING *name, if (check_variable_is_refcursor({STRING_WITH_LEN("OPEN")}, spv)) goto error; - // `OPEN cursor_name FOR ps_name` is not allowed in the grammar - DBUG_ASSERT(stmt->get_ps_name().is_null()); - if (stmt->prepared_stmt.code()) + + /* + It can be either of this: + 1) OPEN c FOR SELECT..; + 2) OPEN c FOR expr; + 3) OPEN c FOR PREPARE stmt; + 4) OPEN c FOR LOCAL spvar_with_stmt_name; + */ + dynamic_count= + (stmt->prepared_stmt.code() != nullptr) + // FOR expr + (!stmt->get_ps_name().is_null()) + // FOR PREPARE + (stmt->prepared_stmt.var().rcontext_handler() != nullptr); // FOR LOCAL + DBUG_ASSERT(dynamic_count <= 1); // They cannot co-exist, by the grammar + + if (dynamic_count) { sphead->m_flags|= sp_head::CONTAINS_DYNAMIC_SQL; DBUG_ASSERT(stmt->sql_command == SQLCOM_END); stmt->sql_command= SQLCOM_EXECUTE; } - /* - This statement is not supported: - OPEN strict_cursor_variable FOR 'SELECT ...'; - It can be rewritten: - - either to use a SELECT statement instead of the dynamic string: - OPEN strict_cursor_variable FOR SELECT ...; - - or to make c0 a weak cursor variable (i.e.without the RETURN clause) - OPEN weak_cursor_variable FOR 'SELECT ...'; - */ return_type_def= dynamic_cast(spv[0].field_def. get_attr_const_generic_ptr(0)); - if (return_type_def && stmt->prepared_stmt.code()) - { - my_error(ER_WRONG_USAGE, MYF(0), name->str, "OPEN..FOR "); - goto error; + if (return_type_def) + { + if (stmt->prepared_stmt.code()) + { + /* + This statement is not supported: + OPEN strict_cursor_variable FOR 'SELECT ...'; + It can be rewritten: + - either to use a SELECT statement instead of the dynamic string: + OPEN strict_cursor_variable FOR SELECT ...; + - or to make c0 a weak cursor variable (i.e.without the RETURN clause) + OPEN weak_cursor_variable FOR 'SELECT ...'; + */ + my_error(ER_WRONG_USAGE, MYF(0), name->str, + "OPEN..FOR "); + goto error; + } + if (stmt->get_ps_name().length) + { + /* + 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; + */ + my_error(ER_WRONG_USAGE, MYF(0), name->str, + "OPEN..FOR PREPARE ps_name"); + goto error; + } + if (stmt->prepared_stmt.var().rcontext_handler()) + { + /* + 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; + */ + my_error(ER_WRONG_USAGE, MYF(0), name->str, + "OPEN..FOR LOCAL ps_name_variable"); + goto error; + } } /* @@ -12892,13 +12933,12 @@ bool LEX::stmt_prepare_validate(const char *stmt_type) } -bool LEX::stmt_prepare(const Lex_ident_sys_st &ident, Item *code) +bool LEX::stmt_prepare(const Lex_sql_statement_name_st &ident, Item *code) { sql_command= SQLCOM_PREPARE; if (stmt_prepare_validate("PREPARE..FROM")) return true; - prepared_stmt.set(ident, code, NULL); - return false; + return prepared_stmt.set(this, ident, code, NULL); } @@ -12913,18 +12953,19 @@ bool LEX::stmt_execute_immediate(Item *code, List *params) } -bool LEX::stmt_execute(const Lex_ident_sys_st &ident, List *params) +bool LEX::stmt_execute(const Lex_sql_statement_name_st &ident, + List *params) { sql_command= SQLCOM_EXECUTE; - prepared_stmt.set(ident, NULL, params); - return stmt_prepare_validate("EXECUTE..USING"); + return prepared_stmt.set(this, ident, NULL, params) || + stmt_prepare_validate("EXECUTE..USING"); } -void LEX::stmt_deallocate_prepare(const Lex_ident_sys_st &ident) +bool LEX::stmt_deallocate_prepare(const Lex_sql_statement_name_st &ident) { sql_command= SQLCOM_DEALLOCATE_PREPARE; - prepared_stmt.set(ident, NULL, NULL); + return prepared_stmt.set(this, ident, NULL, NULL); } diff --git a/sql/sql_lex.h b/sql/sql_lex.h index e86650716896d..0803920e9d96d 100644 --- a/sql/sql_lex.h +++ b/sql/sql_lex.h @@ -79,6 +79,121 @@ class Lex_column_list_privilege: public Lex_column_list_privilege_st }; +/* + A simple or extended prepared statement name. +*/ +struct Lex_sql_statement_name_st +{ + enum Type + { + PREPARED_STATEMENT, // PREPARE stmt FROM 'SELECT 1'; + LOCAL_VAR // PREPARE LOCAL spvar FROM 'SELECT 1'; + }; +private: + Type m_type; + Lex_ident_sys_st m_name; // PS name or spvar name +public: + static Lex_sql_statement_name_st make_for_ps_name( + const Lex_ident_sys_st &name) + { + Lex_sql_statement_name_st res; + res.m_type= PREPARED_STATEMENT; + res.m_name= name; + return res; + } + static Lex_sql_statement_name_st make_for_local_var_name( + const Lex_ident_sys_st &name) + { + Lex_sql_statement_name_st res; + res.m_type= LOCAL_VAR; + res.m_name= name; + return res; + } + Lex_ident_sys_st ps_name() const + { + return m_type == PREPARED_STATEMENT ? m_name : Lex_ident_sys(); + } + Lex_ident_sys_st local_var_name() const + { + return m_type == LOCAL_VAR ? m_name : Lex_ident_sys(); + } +}; + + +/* + This structure stores the tail of the "OPEN c FOR" statement. + The whole structure must satisfy "union check size" assert in sql_yacc.yy. + So we have to use a union here. +*/ +struct Lex_open_for_st +{ + enum Type + { + QUERY, // OPEN c FOR SELECT 1; + DYNAMIC_STRING, // OPEN c FOR expr; + PREPARED_STATEMENT, // OPEN c FOR PREPARE stmt; + LOCAL_VAR // OPEN c FOR LOCAL spvar; + }; +private: + Type m_type; + 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; + }; +public: + static Lex_open_for_st make_for_query() + { + Lex_open_for_st res; + res.m_type= QUERY; + res.m_name= Lex_ident_sys(); + res.m_dynamic_string= nullptr; + return res; + } + static Lex_open_for_st make_for_dynamic_string(Item *dynamic_string) + { + Lex_open_for_st res; + res.m_type= DYNAMIC_STRING; + res.m_name= Lex_ident_sys(); + res.m_dynamic_string= dynamic_string; + return res; + } + static Lex_open_for_st make_for_ps_name(const Lex_ident_sys_st &name) + { + Lex_open_for_st res; + res.m_type= PREPARED_STATEMENT; + res.m_dynamic_string= nullptr; + res.m_name= name; + return res; + } + static Lex_open_for_st make_for_local_var_name(const Lex_ident_sys_st &name) + { + Lex_open_for_st res; + res.m_type= LOCAL_VAR; + res.m_dynamic_string= nullptr; + res.m_name= name; + return res; + } + Lex_ident_sys_st ps_name() const + { + return m_type == PREPARED_STATEMENT ? m_name : Lex_ident_sys(); + } + Lex_ident_sys_st local_var_name() const + { + return m_type == LOCAL_VAR ? m_name : Lex_ident_sys(); + } + Item *dynamic_string() const + { + return m_type == DYNAMIC_STRING ? m_dynamic_string : nullptr; + } +}; + + /** ORDER BY ... LIMIT parameters; */ @@ -3129,13 +3244,14 @@ class Query_arena_memroot; class Lex_prepared_stmt { - Lex_ident_sys m_name; // Statement name (in all queries) - Item *m_code; // PREPARE or EXECUTE IMMEDIATE source expression - List m_params; // List of parameters for EXECUTE [IMMEDIATE] + Lex_ident_sys m_name; // Statement name (in all queries) + Item *m_code; // PREPARE or EXECUTE IMMEDIATE source expression + sp_rcontext_addr m_var; // OPEN c FOR LOCAL spvar; + List m_params; // List of parameters for EXECUTE [IMMEDIATE] public: Lex_prepared_stmt() - :m_code(NULL) + :m_code(NULL), m_var(nullptr, 0) { } const Lex_ident_sys &name() const { @@ -3145,6 +3261,10 @@ class Lex_prepared_stmt { return m_code; } + const sp_rcontext_addr & var() const + { + return m_var; + } uint param_count() const { return m_params.elements; @@ -3158,9 +3278,15 @@ class Lex_prepared_stmt DBUG_ASSERT(m_params.elements == 0); m_name= ident; m_code= code; + m_var= sp_rcontext_addr(nullptr, 0); if (params) m_params= *params; } + bool set(LEX *lex, const Lex_sql_statement_name_st &name, Item *code, + List *params); + + bool set(LEX *lex, const Lex_open_for_st &open_for, + List *params); bool params_fix_fields(THD *thd) { // Fix Items in the EXECUTE..USING list @@ -3172,6 +3298,10 @@ class Lex_prepared_stmt } return false; } + Lex_ident_sys evaluate_name(THD *thd, + String *value_buffer, + String *converter_buffer, + const char *op) const; bool get_dynamic_sql_string(THD *thd, LEX_CSTRING *dst, String *buffer); void lex_start() { @@ -5052,10 +5182,10 @@ struct LEX: public Query_tables_list bool stmt_uninstall_plugin_by_soname(const DDL_options_st &opt, const LEX_CSTRING &soname); bool stmt_prepare_validate(const char *stmt_type); - bool stmt_prepare(const Lex_ident_sys_st &ident, Item *code); - bool stmt_execute(const Lex_ident_sys_st &ident, List *params); + bool stmt_prepare(const Lex_sql_statement_name_st &ident, Item *code); + bool stmt_execute(const Lex_sql_statement_name_st &ident, List *params); bool stmt_execute_immediate(Item *code, List *params); - void stmt_deallocate_prepare(const Lex_ident_sys_st &ident); + bool stmt_deallocate_prepare(const Lex_sql_statement_name_st &ident); bool stmt_alter_table_exchange_partition(Table_ident *table); bool stmt_alter_table(Table_ident *table); diff --git a/sql/sql_prepare.cc b/sql/sql_prepare.cc index 6bfa6857dca1d..5949e81ebebd9 100644 --- a/sql/sql_prepare.cc +++ b/sql/sql_prepare.cc @@ -245,6 +245,10 @@ class Prepared_statement: public Statement select_result *result_arg, Server_side_cursor **cursor_arg, const InstrSlice &instrs_set_placeholder); + static Prepared_statement *find_by_name(THD *thd, const LEX_CSTRING &name) + { + return (Prepared_statement*) thd->stmt_map.find_by_name(&name); + } static Prepared_statement *find_by_name_or_error(THD *thd, const LEX_CSTRING &name, const char *clause) @@ -2802,6 +2806,83 @@ bool Lex_prepared_stmt::get_dynamic_sql_string(THD *thd, } +bool Lex_prepared_stmt::set(LEX *lex, const Lex_sql_statement_name_st &name, + Item *code, List *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 *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; +} + + +/* + Evaluate a simple or an extended prepared statement name. +*/ +Lex_ident_sys Lex_prepared_stmt::evaluate_name(THD *thd, + String *value_buffer, + String *converter_buffer, + const char *op) const +{ + // Simple statement name and extended statement name cannot co-exist + DBUG_ASSERT(m_name.length == 0 || m_var.rcontext_handler() == nullptr); + + if (!m_var.rcontext_handler()) + return m_name; // Simple name: PREPARE stmt FROM 'SELECT 1'; + + // Extended name: PREPARE LOCAL spvar FROM 'SELECT 1'; + DBUG_ASSERT(thd->spcont); + Item *item= thd->get_variable(m_var); + if (!(item= thd->sp_fix_func_item(&item))) + return Lex_ident_sys(); + + String *var_value; + if (!(var_value= item->val_str(value_buffer, converter_buffer, + system_charset_info))) + { + my_error(ER_UNKNOWN_STMT_HANDLER, MYF(0), (int) 4, "NULL", op); + return Lex_ident_sys(); + } + LEX_CSTRING tmp= var_value->to_lex_cstring(); + return Lex_ident_sys(tmp.str, tmp.length); +} + + /** SQLCOM_PREPARE implementation. @@ -2818,14 +2899,20 @@ bool Lex_prepared_stmt::get_dynamic_sql_string(THD *thd, void mysql_sql_stmt_prepare(THD *thd) { + DBUG_ENTER("mysql_sql_stmt_prepare"); LEX *lex= thd->lex; CSET_STRING orig_query= thd->query_string; - const LEX_CSTRING *name= &lex->prepared_stmt.name(); + StringBuffer<64> value_buffer, converter_buffer; + const Lex_ident_sys name= lex->prepared_stmt.evaluate_name(thd, + &value_buffer, + &converter_buffer, + "PREPARE"); + if (name.str == nullptr) + DBUG_VOID_RETURN; // `LOCAL spvar` evaluated to SQL NULL Prepared_statement *stmt; LEX_CSTRING query; - DBUG_ENTER("mysql_sql_stmt_prepare"); - if ((stmt= (Prepared_statement*) thd->stmt_map.find_by_name(name))) + if ((stmt= (Prepared_statement*) thd->stmt_map.find_by_name(&name))) { /* If there is a statement with the same name, remove it. It is ok to @@ -2854,7 +2941,7 @@ void mysql_sql_stmt_prepare(THD *thd) stmt->set_sql_prepare(); /* Set the name first, insert should know that this statement has a name */ - if (stmt->set_name(name)) + if (stmt->set_name(&name)) { delete stmt; DBUG_VOID_RETURN; @@ -3646,7 +3733,14 @@ bool mysql_sql_stmt_execute(THD *thd, const Lex_ident_sys &name, void mysql_sql_stmt_execute(THD *thd) { - (void) mysql_sql_stmt_execute(thd, thd->lex->prepared_stmt.name(), + StringBuffer<64> value_buffer, converter_buffer; + const Lex_ident_sys name= thd->lex->prepared_stmt.evaluate_name(thd, + &value_buffer, + &converter_buffer, + "EXECUTE"); + if (name.str == nullptr) + return; // `LOCAL spvar` evaluated to SQL NULL + (void) mysql_sql_stmt_execute(thd, name, "EXECUTE", false, nullptr, nullptr); } @@ -3826,13 +3920,19 @@ void mysqld_stmt_close(THD *thd, char *packet) void mysql_sql_stmt_close(THD *thd) { Prepared_statement* stmt; - const LEX_CSTRING *name= &thd->lex->prepared_stmt.name(); - DBUG_PRINT("info", ("DEALLOCATE PREPARE: %.*s", (int) name->length, - name->str)); - - if (! (stmt= (Prepared_statement*) thd->stmt_map.find_by_name(name))) + StringBuffer<64> value_buffer, converter_buffer; + const Lex_ident_sys name= thd->lex->prepared_stmt.evaluate_name(thd, + &value_buffer, + &converter_buffer, + "DEALLOCATE PREPARE"); + if (name.str == nullptr) + return; // `LOCAL spvar` evaluated to SQL NULL + DBUG_PRINT("info", ("DEALLOCATE PREPARE: %.*s", (int) name.length, + name.str)); + + if (! (stmt= (Prepared_statement*) thd->stmt_map.find_by_name(&name))) my_error(ER_UNKNOWN_STMT_HANDLER, MYF(0), - static_cast(name->length), name->str, "DEALLOCATE PREPARE"); + static_cast(name.length), name.str, "DEALLOCATE PREPARE"); else if (stmt->is_in_use()) my_error(ER_PS_NO_RECURSION, MYF(0)); else diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy index 269d497058c2f..c57d166ff1c2b 100644 --- a/sql/sql_yacc.yy +++ b/sql/sql_yacc.yy @@ -264,6 +264,8 @@ void _CONCAT_UNDERSCORED(turn_parser_debug_on,yyparse)() bool with_unique_keys; ulong type_constraint; } json_predicate; + Lex_open_for_st open_for; + Lex_sql_statement_name_st sql_statement_name; /* pointers */ Lex_ident_sys *ident_sys_ptr; @@ -1619,7 +1621,12 @@ bool my_yyoverflow(short **a, YYSTYPE **b, size_t *yystacksize); opt_versioning_interval_start json_default_literal set_expr_misc - select_or_expr + +%type + sql_statement_name + +%type + open_for %type opt_vers_auto_part @@ -2229,9 +2236,10 @@ verb_clause: ; deallocate: - deallocate_or_drop PREPARE_SYM ident + deallocate_or_drop PREPARE_SYM sql_statement_name { - Lex->stmt_deallocate_prepare($3); + if (Lex->stmt_deallocate_prepare($3)) + MYSQL_YYABORT; } ; @@ -2241,7 +2249,7 @@ deallocate_or_drop: ; prepare: - PREPARE_SYM ident FROM + PREPARE_SYM sql_statement_name FROM { thd->where= THD_WHERE::USE_WHERE_STRING; thd->where_str= @@ -2256,7 +2264,7 @@ prepare: ; execute: - EXECUTE_SYM ident execute_using + EXECUTE_SYM sql_statement_name execute_using { if (Lex->stmt_execute($2, $3)) MYSQL_YYABORT; @@ -3776,9 +3784,36 @@ select_or_ps_name: | ident { $$= $1; } ; -select_or_expr: - select_for_open_cursor { $$= nullptr; } - | expr { $$= $1; } + +sql_statement_name: + ident + { + $$= Lex_sql_statement_name_st::make_for_ps_name($1); + } + | LOCAL_SYM ident + { + $$= Lex_sql_statement_name_st::make_for_local_var_name($2); + } + ; + + +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); + } + | PREPARE_SYM ident // OPEN c FOR PREPARE stmt; + { + $$= Lex_open_for_st::make_for_ps_name($2); + } + | LOCAL_SYM ident + { + $$= Lex_open_for_st::make_for_local_var_name($2); + } ; sp_cursor_stmt: @@ -3808,12 +3843,13 @@ sp_cursor_stmt_for_open: MYSQL_YYABORT; Lex->clause_that_disallows_subselect= "OPEN..FOR"; } - remember_name select_or_expr remember_end + remember_name open_for remember_end { Lex->clause_that_disallows_subselect= NULL; - $1->prepared_stmt.set(Lex_ident_sys(), $4, nullptr); + if ($1->prepared_stmt.set(Lex, $4, nullptr)) + MYSQL_YYABORT; DBUG_ASSERT(Lex == $1); - if ($1->sp_cursor_stmt_finalize(thd, Lex_ident_sys(), $3, $5)) + if ($1->sp_cursor_stmt_finalize(thd, $4.ps_name(), $3, $5)) MYSQL_YYABORT; $$= $1;